@@ -139,6 +139,8 @@ export type NodeEvaluators = Partial<
139139 }
140140> ;
141141
142+ // Use the same parser, but starting from the expression entry point, to parse
143+ // expression strings on the fly after macro expansion
142144const expressionParser = ( src : string ) : PreprocessorAstNode =>
143145 formatError ( parser ) ( src , {
144146 grammarSource : 'expression' ,
@@ -488,23 +490,30 @@ export type PreprocessorOptions = {
488490 startRule ?: 'constant_expression' | 'program' ;
489491} ;
490492
491- // Evaluate a raw #if / #elif expression string against the current macros.
492- // Handles defined() before macro expansion (spec-correct order), then
493- // parses the result as a constant_expression AST and evaluates it.
493+ // Expressions are stored as strings in the AST, since their contents may be
494+ // affected by macro expansion. This function performs macro expansion, then
495+ // parses and evaluates the expanded string. It uses the same grammar, but
496+ // starting from the "constant expression" rule to parse, aka the rule for the
497+ // expression of an if / elif
494498const evaluateExpressionString = ( expr : string , macros : Macros ) : any => {
495499 // Strip inline comments so "defined/**/A" is treated as "defined A"
496500 const stripped = preprocessComments ( expr ) ;
497- // Step 1: evaluate defined(X) / defined X before any macro expansion
498- const withDefined = stripped
501+
502+ // In the input
503+ // #define A
504+ // #if !defined(A)
505+ // If we expand macros then evaluate the expression it will break. The spec
506+ // says that identifiers inside defined() are not eligible for expansion. So
507+ // hacky way to evaluate them first before macro expansion and parsing
508+ const defined = stripped
499509 . replace ( / d e f i n e d \s * \( \s * ( [ A - Z a - z _ ] [ A - Z a - z _ 0 - 9 ] * ) \s * \) / g, ( _ , id ) =>
500510 id in macros ? '1' : '0'
501511 )
502512 . replace ( / d e f i n e d \s + ( [ A - Z a - z _ ] [ A - Z a - z _ 0 - 9 ] * ) / g, ( _ , id ) =>
503513 id in macros ? '1' : '0'
504514 ) ;
505- // Step 2: expand all macros in the resulting string
506- const expanded = expandMacros ( withDefined , macros ) ;
507- // Step 3: parse the expanded string as a constant_expression and evaluate
515+
516+ const expanded = expandMacros ( defined , macros ) ;
508517 return evaluteExpression ( expressionParser ( expanded . trim ( ) ) , macros ) ;
509518} ;
510519
0 commit comments