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 entry points:
60!! - @link fpx_operators::evaluate_expression evaluate_expression @endlink:
61!! tokenize and evaluate an expression in one call.
62!! - @link fpx_operators::parse_expression parse_expression @endlink:
63!! evaluate an already-tokenized expression.
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.
68!!
69!! @section operator_examples Examples
70!!
71!! 1. Evaluate a simple expression:
72!! @code{.f90}
73!! logical :: ok
74!! integer :: value
75!!
76!! ok = evaluate_expression('1 + 2 * 3', macros, value)
77!!
78!! ! value = 7
79!! ...
80!! @endcode
81!!
82!! 2. Use the `defined` operator:
83!! @code{.f90}
84!! call add(macros, 'DEBUG')
85!!
86!! if (evaluate_expression('defined(DEBUG)', macros)) then
87!! ...
88!! end if
89!! ...
90!! @endcode
91!!
92!! 3. Evaluate expressions involving macros:
93!! @code{.f90}
94!! call add(macros, 'LEVEL', '2')
95!!
96!! if (evaluate_expression('LEVEL >= 2', macros)) then
97!! ...
98!! end if
99!! ...
100!! @endcode
101!!
102!! 4. Conditional operator:
103!! @code{.f90}
104!! call evaluate_expression('1 ? 10 : 20', macros, value)
105!! ! value = 10
106!! ...
107!! @endcode
108module fpx_operators
109 use fpx_global
110 use fpx_string
111 use fpx_constants
112 use fpx_macro
113 use fpx_logging
114 use fpx_token
115 use fpx_context
116
117 implicit none; private
118
119 public :: evaluate_expression, &
121
122 !> Evaluates a preprocessor-style expression with macro substitution.
123 !! Tokenizes the input expression, expands macros where appropriate,
124 !! parses it according to operator precedence, and computes the integer result.
125 !! Returns .true. if evaluation succeeded and the result is non-zero.
126 !!
127 !! @section evaluate_expression_examples Examples
128 !!
129 !! @code{.f90}
130 !! integer :: value
131 !!
132 !! call add(macros, 'SIZE', '64')
133 !!
134 !! if (evaluate_expression('SIZE >= 32', macros, value)) then
135 !! print *, value ! 1
136 !! end if
137 !! ...
138 !! @endcode
139 !!
140 !! @section evaluate_expression_constructors Constructors
141 !!
142 !! @b Constructor
143 !! @code{.f90}
144 !! logical function evaluate(expr, macros, val)
145 !! @endcode
146 !!
147 !! @param[in] expr
148 !! Expression string to evaluate
149 !! @param[inout] macros
150 !! Array of defined macros for substitution and `defined()` checks
151 !! @param[out] val
152 !! (optional) integer result of the evaluation
153 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
154 !!
155 !! @b Constructor
156 !! @code{.f90}
157 !! logical function evaluate(expr, macros, ctx, val)
158 !! @endcode
159 !!
160 !! @param[in] expr
161 !! Expression string to evaluate
162 !! @param[inout] macros
163 !! Array of defined macros for substitution and `defined()` checks
164 !! @param[in] ctx
165 !! Current context
166 !! @param[out] val
167 !! (optional) integer result of the evaluation
168 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
169 !!
170 !! @ingroup group_operators
172 module procedure :: evaluate_expression_default
173 module procedure :: evaluate_expression_with_context
174 end interface
175
176contains
177
178 !> Evaluates a preprocessor-style expression with macro substitution.
179 !! Tokenizes the input expression, expands macros where appropriate,
180 !! parses it according to operator precedence, and computes the integer result.
181 !! Returns .true. if evaluation succeeded and the result is non-zero.
182 !!
183 !! @param[in] expr Expression string to evaluate
184 !! @param[inout] macros Array of defined macros for substitution and `defined()` checks
185 !! @param[out] val (optional) integer result of the evaluation
186 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
187 !!
188 !! @ingroup group_operators
189 logical function evaluate_expression_default(expr, macros, val) result(res)
190 character(*), intent(in) :: expr
191 type(macro), allocatable, intent(inout) :: macros(:)
192 integer, intent(out), optional :: val
193 !private
194 type(context) :: ctx
195
196 ctx = context(expr, 1, '')
197 res = evaluate_expression(expr, macros, ctx, val)
198 end function
199
200 !> Evaluates a preprocessor-style expression with macro substitution.
201 !! Tokenizes the input expression, expands macros where appropriate,
202 !! parses it according to operator precedence, and computes the integer result.
203 !! Returns .true. if evaluation succeeded and the result is non-zero.
204 !!
205 !! @param[in] expr Expression string to evaluate
206 !! @param[inout] macros Array of defined macros for substitution and `defined()` checks
207 !! @param[in] ctx Context
208 !! @param[out] val (optional) integer result of the evaluation
209 !! @return .true. if the expression evaluated successfully to non-zero, .false. otherwise
210 !!
211 !! @ingroup group_operators
212 logical function evaluate_expression_with_context(expr, macros, ctx, val) result(res)
213 character(*), intent(in) :: expr
214 type(macro), allocatable, intent(inout) :: macros(:)
215 type(context), intent(in) :: ctx
216 integer, intent(out), optional :: val
217 !private
218 type(token), allocatable :: tokens(:)
219 integer :: ntokens, pos, result
220
221 call tokenize(expr, tokens, ntokens)
222 if (ntokens == 0) then
223 call printf(render(diagnostic_report(level_error, &
224 message='Tokenization failed', &
225 label=label_type('No tokens found', 1, len_trim(expr)), &
226 source=trim(ctx%path)), &
227 expr, ctx%line))
228 res = .false.
229 return
230 end if
231
232 pos = 1
233 result = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
234 if (pos <= ntokens) then
235 call printf(render(diagnostic_report(level_error, &
236 message='Tokenization failed', &
237 label=label_type('Extra tokens found', tokens(pos)%start, len_trim(tokens(pos)%value)), &
238 source=trim(ctx%path)), &
239 expr, ctx%line))
240 res = .false.
241 return
242 end if
243 res = (result /= 0)
244 if (present(val)) val = result
245 end function
246
247 !! @name Expression parsing hierarchy
248 !! @{
249 !> Parse and evaluate an already-tokenized expression.
250 !!
251 !! This routine implements the top-level non-terminal of the recursive
252 !! descent parser. It is primarily intended for internal use by
253 !! `evaluate_expression`, but remains public to allow external users to
254 !! reuse the parser on custom token streams.
255 !!
256 !! @param[in] expr Expression to be processed
257 !! @param[in] tokens Array of tokens to parse
258 !! @param[in] ntokens Number of valid tokens in the array
259 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
260 !! @param[inout] macros Defined macros for expansion and `defined()` checks
261 !! @param[in] ctx Context
262 !! @return Integer value of the parsed expression
263 !!
264 !! @ingroup group_operators
265 recursive integer function parse_expression(expr, tokens, ntokens, pos, macros, ctx) result(val)
266 character(*), intent(in) :: expr
267 type(token), intent(in) :: tokens(:)
268 integer, intent(in) :: ntokens
269 integer, intent(inout) :: pos
270 type(macro), allocatable, intent(inout) :: macros(:)
271 type(context), intent(in) :: ctx
272
273 val = parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
274 end function
275
276 !> Parses conditional expressions (?:). Right-associative.
277 !! @param[in] expr Expression to be processed
278 !! @param[in] tokens Array of tokens to parse
279 !! @param[in] ntokens Number of valid tokens in the array
280 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
281 !! @param[inout] macros Defined macros for expansion and `defined()` checks
282 !! @param[in] ctx Context
283 !! @return Integer value of the parsed expression
284 !!
285 !! @ingroup group_operators
286 recursive integer function parse_conditional(expr, tokens, ntokens, pos, macros, ctx) result(val)
287 character(*), intent(in) :: expr
288 type(token), intent(in) :: tokens(:)
289 integer, intent(in) :: ntokens
290 integer, intent(inout) :: pos
291 type(macro), allocatable, intent(inout) :: macros(:)
292 type(context), intent(in) :: ctx
293 !private
294 integer :: condition, true_val, false_val
295
296 ! First parse condition at higher precedence
297 condition = parse_or(expr, tokens, ntokens, pos, macros, ctx)
298 if (pos > ntokens) then
299 val = condition
300 return
301 end if
302 ! Check for '?'
303 if (pos <= ntokens .and. tokens(pos)%value == '?') then
304 pos = pos + 1
305 ! Parse true expression (full expression allowed)
306 true_val = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
307
308 ! Expect ':'
309 if (pos > ntokens .or. tokens(pos)%value /= ':') then
310 call printf(render(diagnostic_report(level_error, &
311 message='Syntax error', &
312 label=label_type('Expected ":" in conditional expression', 1, len(expr)), &
313 source=trim(ctx%path)), &
314 expr, ctx%line))
315 val = 0
316 return
317 end if
318
319 pos = pos + 1
320
321 ! Parse false expression (right-associative)
322 false_val = parse_conditional(expr, tokens, ntokens, pos, macros, ctx)
323
324 ! Evaluate condition
325 val = merge(true_val, false_val, condition /= 0)
326 else
327 val = condition
328 end if
329
330 end function
331
332 !> Parses logical OR expressions (`||`).
333 !! @param[in] expr Expression to be processed
334 !! @param[in] tokens Array of tokens to parse
335 !! @param[in] ntokens Number of valid tokens in the array
336 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
337 !! @param[inout] macros Defined macros for expansion and `defined()` checks
338 !! @param[in] ctx Context
339 !! @return Integer value of the parsed expression
340 !!
341 !! @ingroup group_operators
342 recursive integer function parse_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_and(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 = merge(1, 0, left /= 0 .or. parse_and(expr, tokens, ntokens, pos, macros, ctx) /= 0)
360 left = val
361 end do
362 val = left
363 end function
364
365 !> Parses logical AND 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 !! @ingroup group_operators
375 recursive integer function parse_and(expr, tokens, ntokens, pos, macros, ctx) result(val)
376 character(*), intent(in) :: expr
377 type(token), intent(in) :: tokens(:)
378 integer, intent(in) :: ntokens
379 integer, intent(inout) :: pos
380 type(macro), allocatable, intent(inout) :: macros(:)
381 type(context), intent(in) :: ctx
382 !private
383 integer :: left
384
385 left = parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx)
386 if (pos > ntokens) then
387 val = left
388 return
389 end if
390 do while (pos <= ntokens .and. tokens(pos)%value == '&&')
391 pos = pos + 1
392 val = merge(1, 0, left /= 0 .and. parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx) /= 0)
393 left = val
394 end do
395 val = left
396 end function
397
398 !> Parses bitwise OR expressions (`|`).
399 !! @param[in] expr Expression to be processed
400 !! @param[in] tokens Array of tokens to parse
401 !! @param[in] ntokens Number of valid tokens in the array
402 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
403 !! @param[inout] macros Defined macros for expansion and `defined()` checks
404 !! @param[in] ctx Context
405 !! @return Integer value of the parsed expression
406 !!
407 !! @ingroup group_operators
408 recursive integer function parse_bitwise_or(expr, tokens, ntokens, pos, macros, ctx) result(val)
409 character(*), intent(in) :: expr
410 type(token), intent(in) :: tokens(:)
411 integer, intent(in) :: ntokens
412 integer, intent(inout) :: pos
413 type(macro), allocatable, intent(inout) :: macros(:)
414 type(context), intent(in) :: ctx
415 !private
416 integer :: left
417
418 left = parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
419 if (pos > ntokens) then
420 val = left
421 return
422 end if
423 do while (pos <= ntokens .and. tokens(pos)%value == '|')
424 pos = pos + 1
425 val = parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx)
426 left = ior(left, val)
427 end do
428 val = left
429 end function
430
431 !> Parses bitwise XOR expressions (`^`).
432 !! @param[in] expr Expression to be processed
433 !! @param[in] tokens Array of tokens to parse
434 !! @param[in] ntokens Number of valid tokens in the array
435 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
436 !! @param[inout] macros Defined macros for expansion and `defined()` checks
437 !! @param[in] ctx Context
438 !! @return Integer value of the parsed expression
439 !!
440 !! @ingroup group_operators
441 recursive integer function parse_bitwise_xor(expr, tokens, ntokens, pos, macros, ctx) result(val)
442 character(*), intent(in) :: expr
443 type(token), intent(in) :: tokens(:)
444 integer, intent(in) :: ntokens
445 integer, intent(inout) :: pos
446 type(macro), allocatable, intent(inout) :: macros(:)
447 type(context), intent(in) :: ctx
448 !private
449 integer :: left
450
451 left = parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
452 if (pos > ntokens) then
453 val = left
454 return
455 end if
456 do while (pos <= ntokens .and. tokens(pos)%value == '^')
457 pos = pos + 1
458 val = parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx)
459 left = ieor(left, val)
460 end do
461 val = left
462 end function
463
464 !> Parses bitwise AND expressions (`&`).
465 !! @param[in] expr Expression to be processed
466 !! @param[in] tokens Array of tokens to parse
467 !! @param[in] ntokens Number of valid tokens in the array
468 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
469 !! @param[inout] macros Defined macros for expansion and `defined()` checks
470 !! @param[in] ctx Context
471 !! @return Integer value of the parsed expression
472 !!
473 !! @ingroup group_operators
474 recursive integer function parse_bitwise_and(expr, tokens, ntokens, pos, macros, ctx) result(val)
475 character(*), intent(in) :: expr
476 type(token), intent(in) :: tokens(:)
477 integer, intent(in) :: ntokens
478 integer, intent(inout) :: pos
479 type(macro), allocatable, intent(inout) :: macros(:)
480 type(context), intent(in) :: ctx
481 !private
482 integer :: left
483
484 left = parse_equality(expr, tokens, ntokens, pos, macros, ctx)
485 if (pos > ntokens) then
486 val = left
487 return
488 end if
489 do while (pos <= ntokens .and. tokens(pos)%value == '&')
490 pos = pos + 1
491 val = parse_equality(expr, tokens, ntokens, pos, macros, ctx)
492 left = iand(left, val)
493 end do
494 val = left
495 end function
496
497 !> Parses equality/inequality expressions (`==`, `!=`).
498 !! @param[in] expr Expression to be processed
499 !! @param[in] tokens Array of tokens to parse
500 !! @param[in] ntokens Number of valid tokens in the array
501 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
502 !! @param[inout] macros Defined macros for expansion and `defined()` checks
503 !! @param[in] ctx Context
504 !! @return Integer value of the parsed expression
505 !!
506 !! @ingroup group_operators
507 recursive integer function parse_equality(expr, tokens, ntokens, pos, macros, ctx) result(val)
508 character(*), intent(in) :: expr
509 type(token), intent(in) :: tokens(:)
510 integer, intent(in) :: ntokens
511 integer, intent(inout) :: pos
512 type(macro), allocatable, intent(inout) :: macros(:)
513 type(context), intent(in) :: ctx
514 !private
515 integer :: left, right
516
517 left = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
518 if (pos > ntokens) then
519 val = left
520 return
521 end if
522 do while (pos <= ntokens .and. (tokens(pos)%value == '==' .or. tokens(pos)%value == '!='))
523 if (tokens(pos)%value == '==') then
524 pos = pos + 1
525 right = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
526 val = merge(1, 0, left == right)
527 else
528 pos = pos + 1
529 right = parse_relational(expr, tokens, ntokens, pos, macros, ctx)
530 val = merge(1, 0, left /= right)
531 end if
532 left = val
533 end do
534 val = left
535 end function
536
537 !> Parses relational expressions (`<`, `>`, `<=`, `>=`).
538 !! @param[in] expr Expression to be processed
539 !! @param[in] tokens Array of tokens to parse
540 !! @param[in] ntokens Number of valid tokens in the array
541 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
542 !! @param[inout] macros Defined macros for expansion and `defined()` checks
543 !! @param[in] ctx Context
544 !! @return Integer value of the parsed expression
545 !!
546 !! @ingroup group_operators
547 recursive integer function parse_relational(expr, tokens, ntokens, pos, macros, ctx) result(val)
548 character(*), intent(in) :: expr
549 type(token), intent(in) :: tokens(:)
550 integer, intent(in) :: ntokens
551 integer, intent(inout) :: pos
552 type(macro), allocatable, intent(inout) :: macros(:)
553 type(context), intent(in) :: ctx
554 !private
555 integer :: left, right
556
557 left = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
558 if (pos > ntokens) then
559 val = left
560 return
561 end if
562 do while (pos <= ntokens .and. (tokens(pos)%value == '<' .or. tokens(pos)%value == '>' .or. &
563 tokens(pos)%value == '<=' .or. tokens(pos)%value == '>='))
564 if (tokens(pos)%value == '<') then
565 pos = pos + 1
566 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
567 val = merge(1, 0, left < right)
568 else if (tokens(pos)%value == '>') then
569 pos = pos + 1
570 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
571 val = merge(1, 0, left > right)
572 else if (tokens(pos)%value == '<=') then
573 pos = pos + 1
574 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
575 val = merge(1, 0, left <= right)
576 else
577 pos = pos + 1
578 right = parse_shifting(expr, tokens, ntokens, pos, macros, ctx)
579 val = merge(1, 0, left >= right)
580 end if
581 left = val
582 end do
583 val = left
584 end function
585
586 !> Parses shift expressions (`<<`, `>>`).
587 !! @param[in] expr Expression to be processed
588 !! @param[in] tokens Array of tokens to parse
589 !! @param[in] ntokens Number of valid tokens in the array
590 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
591 !! @param[inout] macros Defined macros for expansion and `defined()` checks
592 !! @param[in] ctx Context
593 !! @return Integer value of the parsed expression
594 !!
595 !! @ingroup group_operators
596 recursive integer function parse_shifting(expr, tokens, ntokens, pos, macros, ctx) result(val)
597 character(*), intent(in) :: expr
598 type(token), intent(in) :: tokens(:)
599 integer, intent(in) :: ntokens
600 integer, intent(inout) :: pos
601 type(macro), allocatable, intent(inout) :: macros(:)
602 type(context), intent(in) :: ctx
603 !private
604 integer :: left, right
605
606 left = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
607 if (pos > ntokens) then
608 val = left
609 return
610 end if
611 do while (pos <= ntokens .and. (tokens(pos)%value == '<<' .or. tokens(pos)%value == '>>'))
612 if (tokens(pos)%value == '<<') then
613 pos = pos + 1
614 right = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
615 val = lshift(left, right)
616 else
617 pos = pos + 1
618 right = parse_additive(expr, tokens, ntokens, pos, macros, ctx)
619 val = rshift(left, right)
620 end if
621 left = val
622 end do
623 val = left
624 end function
625
626 !> Parses additive expressions (`+`, `-`).
627 !! @param[in] expr Expression to be processed
628 !! @param[in] tokens Array of tokens to parse
629 !! @param[in] ntokens Number of valid tokens in the array
630 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
631 !! @param[inout] macros Defined macros for expansion and `defined()` checks
632 !! @param[in] ctx Context
633 !! @return Integer value of the parsed expression
634 !!
635 !! @ingroup group_operators
636 recursive integer function parse_additive(expr, tokens, ntokens, pos, macros, ctx) result(val)
637 character(*), intent(in) :: expr
638 type(token), intent(in) :: tokens(:)
639 integer, intent(in) :: ntokens
640 integer, intent(inout) :: pos
641 type(macro), allocatable, intent(inout) :: macros(:)
642 type(context), intent(in) :: ctx
643 !private
644 integer :: left, right
645
646 left = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
647 if (pos > ntokens) then
648 val = left
649 return
650 end if
651 do while (pos <= ntokens .and. (tokens(pos)%value == '+' .or. tokens(pos)%value == '-'))
652 if (tokens(pos)%value == '+') then
653 pos = pos + 1
654 right = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
655 val = left + right
656 else
657 pos = pos + 1
658 right = parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx)
659 val = left - right
660 end if
661 left = val
662 end do
663 val = left
664 end function
665
666 !> Parses multiplicative expressions (`*`, `/`, `%`).
667 !! @param[in] expr Expression to be processed
668 !! @param[in] tokens Array of tokens to parse
669 !! @param[in] ntokens Number of valid tokens in the array
670 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
671 !! @param[inout] macros Defined macros for expansion and `defined()` checks
672 !! @param[in] ctx Context
673 !! @return Integer value of the parsed expression
674 !!
675 !! @ingroup group_operators
676 recursive integer function parse_multiplicative(expr, tokens, ntokens, pos, macros, ctx) result(val)
677 character(*), intent(in) :: expr
678 type(token), intent(in) :: tokens(:)
679 integer, intent(in) :: ntokens
680 integer, intent(inout) :: pos
681 type(macro), allocatable, intent(inout) :: macros(:)
682 type(context), intent(in) :: ctx
683 !private
684 integer :: left, right
685
686 left = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
687 if (pos > ntokens) then
688 val = left
689 return
690 end if
691 do while (pos <= ntokens .and. (tokens(pos)%value == '*' .or. tokens(pos)%value == '/' .or. tokens(pos)%value == '%'))
692 if (tokens(pos)%value == '*') then
693 pos = pos + 1
694 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
695 val = left * right
696 else if (tokens(pos)%value == '/') then
697 pos = pos + 1
698 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
699 val = left / right
700 else
701 pos = pos + 1
702 right = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
703 val = modulo(left, right)
704 end if
705 left = val
706 end do
707 val = left
708 end function
709
710 !> Parses exponentiation (`**`). Right-associative.
711 !! @param[in] expr Expression to be processed
712 !! @param[in] tokens Array of tokens to parse
713 !! @param[in] ntokens Number of valid tokens in the array
714 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
715 !! @param[inout] macros Defined macros for expansion and `defined()` checks
716 !! @param[in] ctx Context
717 !! @return Integer value of the parsed expression
718 !!
719 !! @ingroup group_operators
720 recursive integer function parse_power(expr, tokens, ntokens, pos, macros, ctx) result(val)
721 character(*), intent(in) :: expr
722 type(token), intent(in) :: tokens(:)
723 integer, intent(in) :: ntokens
724 integer, intent(inout) :: pos
725 type(macro), allocatable, intent(inout) :: macros(:)
726 type(context), intent(in) :: ctx
727 !private
728 integer :: left, right
729
730 left = parse_atom(expr, tokens, ntokens, pos, macros, ctx)
731 if (pos > ntokens) then
732 val = left
733 return
734 end if
735 if (pos <= ntokens .and. tokens(pos)%value == '**') then
736 pos = pos + 1
737 ! recurse at same precedence level
738 right = parse_power(expr, tokens, ntokens, pos, macros, ctx)
739 val = left**right
740 else
741 val = left
742 end if
743 end function
744
745 !> Parses unary operators (`!`, `-`, `+`, `~`).
746 !! @param[in] expr Expression to be processed
747 !! @param[in] tokens Array of tokens to parse
748 !! @param[in] ntokens Number of valid tokens in the array
749 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
750 !! @param[inout] macros Defined macros for expansion and `defined()` checks
751 !! @param[in] ctx Context
752 !! @return Integer value of the parsed expression
753 !!
754 !! @ingroup group_operators
755 recursive integer function parse_unary(expr, tokens, ntokens, pos, macros, ctx) result(val)
756 character(*), intent(in) :: expr
757 type(token), intent(in) :: tokens(:)
758 integer, intent(in) :: ntokens
759 integer, intent(inout) :: pos
760 type(macro), allocatable, intent(inout) :: macros(:)
761 type(context), intent(in) :: ctx
762
763 if (pos <= ntokens .and. tokens(pos)%value == '!') then
764 pos = pos + 1
765 val = merge(0, 1, parse_unary(expr, tokens, ntokens, pos, macros, ctx) /= 0)
766 else if (pos <= ntokens .and. tokens(pos)%value == '-') then
767 pos = pos + 1
768 val = -parse_unary(expr, tokens, ntokens, pos, macros, ctx)
769 else if (pos <= ntokens .and. tokens(pos)%value == '+') then
770 pos = pos + 1
771 val = parse_unary(expr, tokens, ntokens, pos, macros, ctx)
772 else if (pos <= ntokens .and. tokens(pos)%value == '~') then
773 pos = pos + 1
774 val = not(parse_unary(expr, tokens, ntokens, pos, macros, ctx))
775 else
776 val = parse_power(expr, tokens, ntokens, pos, macros, ctx)
777 end if
778 end function
779
780 !> Parses primary expressions: numbers, identifiers, `defined(...)`, parentheses.
781 !! @param[in] expr Input expression
782 !! @param[in] tokens Array of tokens to parse
783 !! @param[in] ntokens Number of valid tokens in the array
784 !! @param[inout] pos Current parsing position (updated as tokens are consumed)
785 !! @param[inout] macros Defined macros for expansion and `defined()` checks
786 !! @param[in] ctx Context
787 !! @return Integer value of the parsed expression
788 !!
789 !! @ingroup group_operators
790 recursive integer function parse_atom(expr, tokens, ntokens, pos, macros, ctx) result(val)
791 character(*), intent(in) :: expr
792 type(token), intent(in) :: tokens(:)
793 integer, intent(in) :: ntokens
794 integer, intent(inout) :: pos
795 type(macro), allocatable, intent(inout) :: macros(:)
796 type(context), intent(in) :: ctx
797 !private
798 integer :: i
799 character(:), allocatable :: expanded
800 logical :: stitch
801
802 if (pos > ntokens) then
803 call printf(render(diagnostic_report(level_error, &
804 message='Syntax error', &
805 label=label_type('Unexpected end of expression', pos, 1), &
806 source=trim(ctx%path)), &
807 expr, ctx%line))
808 val = 0
809 return
810 end if
811
812 if (tokens(pos)%type == 0) then
813 val = strtol(tokens(pos)%value)
814 pos = pos + 1
815 else if (tokens(pos)%type == 2) then
816 if (is_defined(tokens(pos)%value, macros)) then
817 expanded = expand_macros(tokens(pos)%value, macros, stitch, global%implicit_continuation, &
818 global%support_dollar_insert, ctx)
819 if (.not. evaluate_expression(expanded, macros, ctx, val)) val = 0
820 else
821 val = 0
822 end if
823 pos = pos + 1
824 else if (tokens(pos)%value == '(') then
825 pos = pos + 1
826 val = parse_expression(expr, tokens, ntokens, pos, macros, ctx)
827 if (pos > ntokens .or. tokens(pos)%value /= ')') then
828 call printf(render(diagnostic_report(level_error, &
829 message='Syntax error', &
830 label=label_type('Missing closing parenthesis in expression', len(expr), 1), &
831 source=trim(ctx%path)), &
832 expr, ctx%line))
833 val = 0
834 else
835 pos = pos + 1
836 end if
837 else if (tokens(pos)%type == 4) then
838 expanded = trim(tokens(pos)%value)
839 val = merge(1, 0, is_defined(expanded, macros))
840 pos = pos + 1
841 else
842 call printf(render(diagnostic_report(level_error, &
843 message='Invalid expression', &
844 label=label_type('Unknown token', 1, len_trim(tokens(pos)%value)), &
845 source=trim(ctx%path)), &
846 expr, ctx%line))
847 val = 0
848 pos = pos + 1
849 end if
850 end function
851 !! @}
852end module
type(global_settings), public global
Global preprocessor configuration instance.
Definition global.f90:192
character(:) function, allocatable, public expand_macros(line, macros, stitch, implicit_conti, dollar_insert, ctx)
Recursively expand user-defined macros.
Definition macro.f90:453
logical function, public is_defined(name, macros, idx)
Determine whether a macro is currently defined.
Definition macro.f90:825
recursive integer function, public parse_expression(expr, tokens, ntokens, pos, macros, ctx)
Parse and evaluate an already-tokenized expression.
subroutine, public tokenize(expr, tokens, ntokens)
Tokenizes a preprocessor expression into an array of token structures. Handles whitespace,...
Definition token.f90:159
Generic renderer for diagnostics and source excerpts.
Definition logging.f90:208
Evaluates a preprocessor-style expression with macro substitution. Tokenizes the input expression,...
Return the trimmed length of a string object.
Definition string.f90:201
Return the length of a string object.
Definition string.f90:164
Remove trailing blanks from a string object.
Definition string.f90:240
Converts a string to integer.
Definition token.f90:140
Snapshot of a source location within the preprocessing stream.
Definition context.f90:114
Structured compiler diagnostic.
Definition logging.f90:337
Diagnostic label identifying a region of source text.
Definition logging.f90:304
Representation of a preprocessor macro.
Definition macro.f90:135
Represents a single token in a parsed expression. Holds the string value of the token and its classif...
Definition token.f90:105