Home | History | Annotate | Line # | Download | only in unit-tests
cond-token-plain.mk revision 1.15
      1 # $NetBSD: cond-token-plain.mk,v 1.15 2021/12/30 02:14:55 rillig Exp $
      2 #
      3 # Tests for plain tokens (that is, string literals without quotes)
      4 # in .if conditions.  These are also called bare words.
      5 
      6 .MAKEFLAGS: -dc
      7 
      8 # The word 'value' after the '!=' is a bare word.
      9 .if ${:Uvalue} != value
     10 .  error
     11 .endif
     12 
     13 # Using a '#' in a string literal in a condition leads to a malformed
     14 # condition since comment parsing is done in an early phase and removes the
     15 # '#' and everything after it long before the condition parser gets to see it.
     16 #
     17 # XXX: The error message is missing for this malformed condition.
     18 # The right-hand side of the comparison is just a '"', before unescaping.
     19 .if ${:U} != "#hash"
     20 .  error
     21 .endif
     22 
     23 # To get a '#' into a condition, it has to be escaped using a backslash.
     24 # This prevents the comment parser from removing it, and in turn, it becomes
     25 # visible to CondParser_String.
     26 .if ${:U\#hash} != "\#hash"
     27 .  error
     28 .endif
     29 
     30 # Since 2002-12-30, and still as of 2020-09-11, CondParser_Token handles
     31 # the '#' specially, even though at this point, there should be no need for
     32 # comment handling anymore.  The comments are supposed to be stripped off
     33 # in a very early parsing phase.
     34 #
     35 # See https://gnats.netbsd.org/19596 for example makefiles demonstrating the
     36 # original problems.  At that time, the parser didn't recognize the comment in
     37 # the line '.else # comment3'.  This workaround is not needed anymore since
     38 # comments are stripped in an earlier phase.  See "case '#'" in
     39 # CondParser_Token.
     40 #
     41 # XXX: Missing error message for the malformed condition. The right-hand
     42 # side before unescaping is double-quotes, backslash, backslash.
     43 .if ${:U\\} != "\\#hash"
     44 .  error
     45 .endif
     46 
     47 # The right-hand side of a comparison is not parsed as a token, therefore
     48 # the code from CondParser_Token does not apply to it.
     49 # TODO: Explain the consequences.
     50 # TODO: Does this mean that more syntactic variants are allowed here?
     51 .if ${:U\#hash} != \#hash
     52 .  error
     53 .endif
     54 
     55 # XXX: What is the purpose of treating an escaped '#' in the following
     56 # condition as a comment?  And why only at the beginning of a token,
     57 # just as in the shell?
     58 .if 0 \# This is treated as a comment, but why?
     59 .  error
     60 .endif
     61 
     62 # Ah, ok, this can be used to add an end-of-condition comment.  But does
     63 # anybody really use this?  This is neither documented nor obvious since
     64 # the '#' is escaped.  It's much clearer to write a comment in the line
     65 # above the condition.
     66 .if ${0 \# comment :?yes:no} != no
     67 .  error
     68 .endif
     69 .if ${1 \# comment :?yes:no} != yes
     70 .  error
     71 .endif
     72 
     73 # Usually there is whitespace around the comparison operator, but this is
     74 # not required.
     75 .if ${UNDEF:Uundefined}!=undefined
     76 .  error
     77 .endif
     78 .if ${UNDEF:U12345}>12345
     79 .  error
     80 .endif
     81 .if ${UNDEF:U12345}<12345
     82 .  error
     83 .endif
     84 .if (${UNDEF:U0})||0
     85 .  error
     86 .endif
     87 
     88 # Only the comparison operator terminates the comparison operand, and it's
     89 # a coincidence that the '!' is both used in the '!=' comparison operator
     90 # as well as for negating a comparison result.
     91 #
     92 # The boolean operators '&' and '|' don't terminate a comparison operand.
     93 .if ${:Uvar}&&name != "var&&name"
     94 .  error
     95 .endif
     96 .if ${:Uvar}||name != "var||name"
     97 .  error
     98 .endif
     99 
    100 # A bare word may appear alone in a condition, without any comparison
    101 # operator.  It is implicitly converted into defined(bare).
    102 .if bare
    103 .  error
    104 .else
    105 .  info A bare word is treated like defined(...), and the variable $\
    106 	'bare' is not defined.
    107 .endif
    108 
    109 VAR=	defined
    110 .if VAR
    111 .  info A bare word is treated like defined(...).
    112 .else
    113 .  error
    114 .endif
    115 
    116 # Bare words may be intermixed with variable expressions.
    117 .if V${:UA}R
    118 .  info ok
    119 .else
    120 .  error
    121 .endif
    122 
    123 # In bare words, even undefined variables are allowed.  Without the bare
    124 # words, undefined variables are not allowed.  That feels inconsistent.
    125 .if V${UNDEF}AR
    126 .  info Undefined variables in bare words expand to an empty string.
    127 .else
    128 .  error
    129 .endif
    130 
    131 .if 0${:Ux00}
    132 .  error
    133 .else
    134 .  info Numbers can be composed from literals and variable expressions.
    135 .endif
    136 
    137 .if 0${:Ux01}
    138 .  info Numbers can be composed from literals and variable expressions.
    139 .else
    140 .  error
    141 .endif
    142 
    143 # If the right-hand side is missing, it's a parse error.
    144 .if "" ==
    145 .  error
    146 .else
    147 .  error
    148 .endif
    149 
    150 # If the left-hand side is missing, it's a parse error as well, but without
    151 # a specific error message.
    152 .if == ""
    153 .  error
    154 .else
    155 .  error
    156 .endif
    157 
    158 # The '\\' is not a line continuation.  Neither is it an unquoted string
    159 # literal.  Instead, it is parsed as a bare word (ParseWord),
    160 # and in that context, the backslash is just an ordinary character. The
    161 # function argument thus stays '\\' (2 backslashes).  This string is passed
    162 # to FuncDefined, and since there is no variable named '\\', the condition
    163 # evaluates to false.
    164 .if \\
    165 .  error
    166 .else
    167 .  info The variable '\\' is not defined.
    168 .endif
    169 
    170 ${:U\\\\}=	backslash
    171 .if \\
    172 .  info Now the variable '\\' is defined.
    173 .else
    174 .  error
    175 .endif
    176 
    177 # Anything that doesn't start with a double quote is considered a "bare word".
    178 # Strangely, a bare word may contain double quotes inside.  Nobody should ever
    179 # depend on this since it may well be unintended.  See CondParser_String.
    180 .if "unquoted\"quoted" != unquoted"quoted
    181 .  error
    182 .endif
    183 
    184 # FIXME: In CondParser_String, Var_Parse returns var_Error without a
    185 # corresponding error message.
    186 .if $$$$$$$$ != ""
    187 .  error
    188 .else
    189 .  error
    190 .endif
    191 
    192 # In a condition in an .if directive, the left-hand side must not be an
    193 # unquoted string literal.
    194 # expect+1: Malformed conditional (left == right)
    195 .if left == right
    196 .endif
    197 # Before cond.c 1.276 from 2021-09-21, a variable expression containing the
    198 # modifier ':?:' allowed unquoted string literals for the rest of the
    199 # condition.  This was an unintended implementation mistake.
    200 # expect+1: Malformed conditional (${0:?:} || left == right)
    201 .if ${0:?:} || left == right
    202 .endif
    203 # This affected only the comparisons after the expression, so the following
    204 # was still a syntax error.
    205 # expect+1: Malformed conditional (left == right || ${0:?:})
    206 .if left == right || ${0:?:}
    207 .endif
    208 
    209 # See cond-token-string.mk for similar tests where the condition is enclosed
    210 # in "quotes".
    211 
    212 .MAKEFLAGS: -d0
    213 
    214 
    215 # As of cond.c 1.320 from 2021-12-30, the code in CondParser_ComparisonOrLeaf
    216 # looks suspicious of evaluating the expression twice: first for parsing a
    217 # bare word and second for parsing the left-hand side of a comparison.
    218 #
    219 # In '.if' directives, the left-hand side of a comparison must not be a bare
    220 # word though, and this keeps CondParser_Leaf from evaluating the expression
    221 # for the second time.  The right-hand side of a comparison may be a bare
    222 # word, but that side has no risk of being parsed more than once.
    223 #
    224 # expect+1: Malformed conditional (VAR.${IF_COUNT::+=1} != "")
    225 .if VAR.${IF_COUNT::+=1} != ""
    226 .  error
    227 .else
    228 .  error
    229 .endif
    230 .if ${IF_COUNT} != "1"
    231 .  error
    232 .endif
    233 
    234 # A different situation is when CondParser.leftUnquotedOK is true.  This
    235 # situation arises in expressions of the form ${cond:?yes:no}.  As of
    236 # 2021-12-30, the condition in such an expression is evaluated before parsing
    237 # the condition, see varmod-ifelse.mk.  To pass a variable expression to the
    238 # condition parser, it needs to be escaped.  This rarely happens in practice,
    239 # in most cases the conditions are simple enough that it doesn't matter
    240 # whether the condition is first evaluated and then parsed, or vice versa.
    241 # A half-baked attempt at hiding this implementation detail is
    242 # CondParser.leftUnquotedOK, but that is a rather leaky abstraction.
    243 
    244 #.MAKEFLAGS: -dcv
    245 COND=	VAR.$${MOD_COUNT::+=1}
    246 .if ${${COND} == "VAR.":?yes:no} != "yes"
    247 .  error
    248 .endif
    249 
    250 # The value "1 1" demonstrates that the expression ${MOD_COUNT::+=1} was
    251 # evaluated twice.  In practice, expressions that occur in conditions do not
    252 # have side effects, making this problem rather academic, but it is there.
    253 .if ${MOD_COUNT} != "1 1"
    254 .  error
    255 .endif
    256 #.MAKEFLAGS: -d0
    257