Home | History | Annotate | Line # | Download | only in unit-tests
varmod-indirect.mk revision 1.22
      1 # $NetBSD: varmod-indirect.mk,v 1.22 2025/03/29 16:44:14 rillig Exp $
      2 #
      3 # Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}.
      4 # These can be used for very basic purposes like converting a string to either
      5 # uppercase or lowercase, as well as for fairly advanced modifiers that first
      6 # look like line noise and are hard to decipher.
      7 #
      8 # Initial support for indirect modifiers was added in var.c 1.101 from
      9 # 2006-02-18.  Since var.c 1.108 from 2006-05-11 it is possible to use
     10 # indirect modifiers for all but the very first modifier as well.
     11 
     12 
     13 # To apply a modifier indirectly via another variable, the whole
     14 # modifier must be put into a single expression.
     15 # The following expression generates a parse error since its indirect
     16 # modifier contains more than a sole expression.
     17 #
     18 # expect+1: Unknown modifier "${"
     19 .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}"
     20 .  warning unexpected
     21 .endif
     22 
     23 
     24 # Adding another level of indirection (the 2 nested :U expressions) helps.
     25 .if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement"
     26 .  warning unexpected
     27 .endif
     28 
     29 
     30 # Multiple indirect modifiers can be applied one after another as long as
     31 # they are separated with colons.
     32 .if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE"
     33 .  warning unexpected
     34 .endif
     35 
     36 
     37 # An indirect variable that evaluates to the empty string is allowed.
     38 # It is even allowed to write another modifier directly afterwards.
     39 # There is no practical use case for this feature though, as demonstrated
     40 # in the test case directly below.
     41 .if ${value:L:${:Dempty}S,value,replaced,} != "replaced"
     42 .  warning unexpected
     43 .endif
     44 
     45 # If an expression for an indirect modifier evaluates to anything else than an
     46 # empty string and is neither followed by a ':' nor '}', this produces a parse
     47 # error.  Due to this parse error, this construct cannot be used reasonably
     48 # in practice.
     49 #
     50 # expect+2: Unknown modifier "${"
     51 #.MAKEFLAGS: -dvc
     52 .if ${value:L:${:UM*}S,value,replaced,} == "anything"
     53 .  error
     54 .else
     55 .  error
     56 .endif
     57 #.MAKEFLAGS: -d0
     58 
     59 # An indirect modifier can be followed by other modifiers, no matter if the
     60 # indirect modifier evaluates to an empty string or not.
     61 #
     62 # This makes it possible to define conditional modifiers, like this:
     63 #
     64 # M.little-endian=	S,1234,4321,
     65 # M.big-endian=		# none
     66 .if ${value:L:${:D empty }:S,value,replaced,} != "replaced"
     67 .  error
     68 .endif
     69 
     70 
     71 # The nested expression expands to "tu", and this is interpreted as
     72 # a variable modifier for the value "Upper", resulting in "UPPER".
     73 .if ${Upper:L:${:Utu}} != "UPPER"
     74 .  error
     75 .endif
     76 
     77 # The nested expression expands to "tl", and this is interpreted as
     78 # a variable modifier for the value "Lower", resulting in "lower".
     79 .if ${Lower:L:${:Utl}} != "lower"
     80 .  error
     81 .endif
     82 
     83 
     84 # The nested expression is ${1 != 1:?Z:tl}, consisting of the
     85 # condition "1 != 1", the then-branch "Z" and the else-branch "tl".  Since
     86 # the condition evaluates to false, the then-branch is ignored (it would
     87 # have been an unknown modifier anyway) and the ":tl" modifier is applied.
     88 .if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed"
     89 .  error
     90 .endif
     91 
     92 
     93 # The indirect modifier can also replace an ':L' modifier, which allows for
     94 # brain twisters since by reading the expression alone, it is not possible
     95 # to say whether the variable name will be evaluated as a variable name or
     96 # as the immediate value of the expression.
     97 VAR=	value
     98 M_ExpandVar=	# an empty modifier
     99 M_VarAsValue=	L
    100 #
    101 .if ${VAR:${M_ExpandVar}} != "value"
    102 .  error
    103 .endif
    104 .if ${VAR:${M_VarAsValue}} != "VAR"
    105 .  error
    106 .endif
    107 
    108 # The indirect modifier M_ListToSkip, when applied to a list of patterns,
    109 # expands to a sequence of ':N' modifiers, each of which filters one of the
    110 # patterns.  This list of patterns can then be applied to another variable
    111 # to actually filter that variable.
    112 #
    113 M_ListToSkip=	@pat@N$${pat}@:ts:
    114 #
    115 # The dollar signs need to be doubled in the above modifier expression,
    116 # otherwise they would be expanded too early, that is, when parsing the
    117 # modifier itself.
    118 #
    119 # In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'.
    120 # The 'N' comes from the expression 'N${pat}', the separating colons come
    121 # from the modifier ':ts:'.
    122 #
    123 #.MAKEFLAGS: -dcv		# Uncomment this line to see the details
    124 #
    125 PRIMES=		2 3 5 7 1[1379]
    126 M_NoPrimes=	${PRIMES:${M_ListToSkip}}
    127 .if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20"
    128 .  error
    129 .endif
    130 .MAKEFLAGS: -d0
    131 
    132 
    133 # In contrast to the .if conditions, the .for loop allows undefined
    134 # expressions.  These expressions expand to empty strings.
    135 
    136 # An undefined expression without any modifiers expands to an empty string.
    137 .for var in before ${UNDEF} after
    138 # expect+2: before
    139 # expect+1: after
    140 .  info ${var}
    141 .endfor
    142 
    143 # An undefined expression with only modifiers that keep the expression
    144 # undefined expands to an empty string.
    145 .for var in before ${UNDEF:${:US,a,a,}} after
    146 # expect+2: before
    147 # expect+1: after
    148 .  info ${var}
    149 .endfor
    150 
    151 # Even in an indirect modifier based on an undefined variable, the value of
    152 # the expression in Var_Parse is a simple empty string.
    153 .for var in before ${UNDEF:${:U}} after
    154 # expect+2: before
    155 # expect+1: after
    156 .  info ${var}
    157 .endfor
    158 
    159 # An error in an indirect modifier.
    160 # expect+1: Unknown modifier "Z"
    161 .for var in before ${UNDEF:${:UZ}} after
    162 # expect+2: before
    163 # expect+1: after
    164 .  info ${var}
    165 .endfor
    166 
    167 
    168 # Another slightly different evaluation context is the right-hand side of
    169 # a variable assignment using ':='.
    170 .MAKEFLAGS: -dpv
    171 
    172 # The undefined expression is kept as-is.
    173 _:=	before ${UNDEF} after
    174 
    175 # The undefined expression is kept as-is.
    176 _:=	before ${UNDEF:${:US,a,a,}} after
    177 
    178 # XXX: The subexpression ${:U} is fully defined, therefore it is expanded.
    179 # This results in ${UNDEF:}, which can lead to tricky parse errors later,
    180 # when the variable '_' is expanded further.
    181 #
    182 # XXX: What should be the correct strategy here?  One possibility is to
    183 # expand the defined subexpression and replace it with ${:U...}, just like
    184 # in .for loops.  This would preserve the structure of the expression while
    185 # at the same time expanding the expression as far as possible.
    186 _:=	before ${UNDEF:${:U}} after
    187 
    188 # XXX: This expands to ${UNDEF:Z}, which will behave differently if the
    189 # variable '_' is used in a context where the expression ${_} is
    190 # parsed but not evaluated.
    191 # expect+1: Unknown modifier "Z"
    192 _:=	before ${UNDEF:${:UZ}} after
    193 
    194 .MAKEFLAGS: -d0
    195 .undef _
    196 
    197 
    198 # When evaluating indirect modifiers, these modifiers may expand to ':tW',
    199 # which modifies the interpretation of the expression value. This modified
    200 # interpretation only lasts until the end of the indirect modifier, it does
    201 # not influence the outer expression.
    202 .if ${1 2 3:L:tW:[#]} != 1		# direct :tW applies to the :[#]
    203 .  error
    204 .endif
    205 .if ${1 2 3:L:${:UtW}:[#]} != 3		# indirect :tW does not apply to :[#]
    206 .  error
    207 .endif
    208 
    209 
    210 # When evaluating indirect modifiers, these modifiers may expand to ':ts*',
    211 # which modifies the interpretation of the expression value. This modified
    212 # interpretation only lasts until the end of the indirect modifier, it does
    213 # not influence the outer expression.
    214 #
    215 # In this first expression, the direct ':ts*' has no effect since ':U' does not
    216 # treat the expression value as a list of words but as a single word.  It has
    217 # to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no
    218 # variable of that name.
    219 #.MAKEFLAGS: -dcpv
    220 .if ${1 2 3:L:ts*:Ua b c} != "a b c"
    221 .  error
    222 .endif
    223 # In this expression, the direct ':ts*' affects the ':M' at the end.
    224 .if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c"
    225 .  error
    226 .endif
    227 # In this expression, the ':ts*' is indirect, therefore the changed separator
    228 # only applies to the modifiers from the indirect text.  It does not affect
    229 # the ':M' since that is not part of the text from the indirect modifier.
    230 #
    231 # Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers
    232 # (which creates a new ModChain containing a fresh separator),
    233 # the outer separator character is not passed by reference to the inner
    234 # evaluation, therefore the scope of the inner separator ends after applying
    235 # the modifier ':ts*'.
    236 .if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c"
    237 .  error
    238 .endif
    239 
    240 # A direct modifier ':U' turns the expression from undefined to defined.
    241 # An indirect modifier ':U' has the same effect, unlike the separator from
    242 # ':ts*' or the single-word marker from ':tW'.
    243 #
    244 # This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes
    245 # the definedness of the outer expression by reference.  If that weren't the
    246 # case, the first condition below would result in a parse error because its
    247 # left-hand side would be undefined.
    248 .if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback"
    249 .  error
    250 .endif
    251 .if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback"
    252 .  error
    253 .endif
    254 
    255 
    256 # In parse-only mode, the indirect modifiers must not be evaluated.
    257 #
    258 # Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier
    259 # was partially evaluated (only the variable value, without applying any
    260 # modifiers) and then interpreted as modifiers to the main expression.
    261 #
    262 # The expression ${:UZ} starts with the value "", and in parse-only mode, the
    263 # modifier ':UZ' does not modify the expression value.  This results in an
    264 # empty string for the indirect modifiers, generating no warning.
    265 .if 0 && ${VAR:${:UZ}}
    266 .endif
    267 # The expression ${M_invalid} starts with the value "Z", which is an unknown
    268 # modifier.  Trying to apply this unknown modifier generated a warning.
    269 M_invalid=	Z
    270 .if 0 && ${VAR:${M_invalid}}
    271 .endif
    272 # The ':S' modifier does not change the expression value in parse-only mode,
    273 # keeping the "Z", which is then skipped in parse-only mode.
    274 .if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}}
    275 .endif
    276 # The ':@' modifier does not change the expression value in parse-only mode,
    277 # keeping the "Z", which is then skipped in parse-only mode.
    278 .if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}}
    279 .endif
    280