Home | History | Annotate | Line # | Download | only in unit-tests
directive-for-empty.mk revision 1.1
      1  1.1  rillig # $NetBSD: directive-for-empty.mk,v 1.1 2022/05/23 22:33:56 rillig Exp $
      2  1.1  rillig #
      3  1.1  rillig # Tests for .for loops containing conditions of the form 'empty(var:...)'.
      4  1.1  rillig #
      5  1.1  rillig # When a .for loop is expanded, variable expressions in the body of the loop
      6  1.1  rillig # are replaced with expressions containing the variable values.  This
      7  1.1  rillig # replacement is a bit naive but covers most of the practical cases.  The one
      8  1.1  rillig # popular exception is the condition 'empty(var:Modifiers)', which does not
      9  1.1  rillig # look like a variable expression and is thus not replaced.
     10  1.1  rillig #
     11  1.1  rillig # See also:
     12  1.1  rillig #	https://gnats.netbsd.org/43821
     13  1.1  rillig 
     14  1.1  rillig 
     15  1.1  rillig # In the body of the .for loop, the expression '${i:M*2*}' is replaced with
     16  1.1  rillig # '${:U11:M*2*}', '${:U12:M*2*}', '${:U13:M*2*}', one after another.  This
     17  1.1  rillig # replacement creates the impression that .for variables were real variables,
     18  1.1  rillig # when in fact they aren't.
     19  1.1  rillig .for i in 11 12 13
     20  1.1  rillig .  if ${i:M*2*}
     21  1.1  rillig .info 2
     22  1.1  rillig .  endif
     23  1.1  rillig .endfor
     24  1.1  rillig 
     25  1.1  rillig 
     26  1.1  rillig # In conditions, the function call to 'empty' does not look like a variable
     27  1.1  rillig # expression, therefore it is not replaced.  Since there is no global variable
     28  1.1  rillig # named 'i', this expression makes for a leaky abstraction.  If the .for
     29  1.1  rillig # variables were real variables, calling 'empty' would work on them as well.
     30  1.1  rillig .for i in 11 12 13
     31  1.1  rillig # Asking for an empty iteration variable does not make sense as the .for loop
     32  1.1  rillig # splits the iteration items into words, and such a word cannot be empty.
     33  1.1  rillig .  if empty(i)
     34  1.1  rillig .    error			# due to the leaky abstraction
     35  1.1  rillig .  endif
     36  1.1  rillig # The typical way of using 'empty' with variables from .for loops is pattern
     37  1.1  rillig # matching using the modifiers ':M' or ':N'.
     38  1.1  rillig .  if !empty(i:M*2*)
     39  1.1  rillig .    if ${i} != "12"
     40  1.1  rillig .      error
     41  1.1  rillig .    endif
     42  1.1  rillig .  endif
     43  1.1  rillig .endfor
     44  1.1  rillig 
     45  1.1  rillig 
     46  1.1  rillig # The idea of replacing every occurrences of 'empty(i' in the body of a .for
     47  1.1  rillig # loop would be naive and require many special cases, as there are many cases
     48  1.1  rillig # that need to be considered when deciding whether the token 'empty' is a
     49  1.1  rillig # function call or not, as demonstrated by the following examples.  For
     50  1.1  rillig # variable expressions like '${i:Modifiers}', this is simpler as a single
     51  1.1  rillig # dollar almost always starts a variable expression.  For counterexamples and
     52  1.1  rillig # edge cases, see directive-for-escape.mk.  Adding another such tricky detail
     53  1.1  rillig # is out of the question.
     54  1.1  rillig .MAKEFLAGS: -df
     55  1.1  rillig .for i in value
     56  1.1  rillig # The identifier 'empty' can only be used in conditions such as .if, .ifdef or
     57  1.1  rillig # .elif.  In other lines the string 'empty(' must be preserved.
     58  1.1  rillig CPPFLAGS+=	-Dmessage="empty(i)"
     59  1.1  rillig # There may be whitespace between 'empty' and '('.
     60  1.1  rillig .if ! empty (i)
     61  1.1  rillig .  error
     62  1.1  rillig .endif
     63  1.1  rillig # Even in conditions, the string 'empty(' is not always a function call, it
     64  1.1  rillig # can occur in a string literal as well.
     65  1.1  rillig .if "empty\(i)" != "empty(i)"
     66  1.1  rillig .  error
     67  1.1  rillig .endif
     68  1.1  rillig # In comments like 'empty(i)', the text must be preserved as well.
     69  1.1  rillig #
     70  1.1  rillig # Conditions, including function calls to 'empty', can not only occur in
     71  1.1  rillig # condition directives, they can also occur in the modifier ':?', see
     72  1.1  rillig # varmod-ifelse.mk.
     73  1.1  rillig CPPFLAGS+=	-Dmacro="${empty(i):?empty:not-empty}"
     74  1.1  rillig .endfor
     75  1.1  rillig .MAKEFLAGS: -d0
     76  1.1  rillig 
     77  1.1  rillig 
     78  1.1  rillig # An idea to work around the above problems is to collect the variables from
     79  1.1  rillig # the .for loops in a separate scope.  To match the current behavior, there
     80  1.1  rillig # has to be one scope per included file.  There may be .for loops using the
     81  1.1  rillig # same variable name in files that include each other:
     82  1.1  rillig #
     83  1.1  rillig # outer.mk:	.for i in outer
     84  1.1  rillig #		.  info $i		# outer
     85  1.1  rillig #		.  include "inner.mk"
     86  1.1  rillig # inner.mk:	.    info $i		# (undefined)
     87  1.1  rillig #		.    for i in inner
     88  1.1  rillig #		.      info $i		# inner
     89  1.1  rillig #		.    endfor
     90  1.1  rillig #		.    info $i		# (undefined)
     91  1.1  rillig # outer.mk:	.  info $i		# outer
     92  1.1  rillig #		.endfor
     93  1.1  rillig #
     94  1.1  rillig # This might be regarded another leaky abstraction, but it is in fact useful
     95  1.1  rillig # that variables from .for loops can only affect expressions in the current
     96  1.1  rillig # file.  If variables from .for loops were implemented as global variables,
     97  1.1  rillig # they might interact between files.
     98  1.1  rillig #
     99  1.1  rillig # To emulate this exact behavior for the function 'empty', each file in the
    100  1.1  rillig # stack of included files needs its own scope that is independent from the
    101  1.1  rillig # other files.
    102  1.1  rillig #
    103  1.1  rillig # Another tricky detail are nested .for loops in a single file that use the
    104  1.1  rillig # same variable name.  These are generally avoided by developers, as they
    105  1.1  rillig # would be difficult to understand for humans as well.  Technically, they are
    106  1.1  rillig # possible though.  Assuming there are two nested .for loops, both using the
    107  1.1  rillig # variable 'i'.  When the inner .for loop ends, the inner 'i' needs to be
    108  1.1  rillig # removed from the scope, which would need to make the outer 'i' visible
    109  1.1  rillig # again.  This would suggest to use one variable scope per .for loop.
    110  1.1  rillig #
    111  1.1  rillig # Using a separate scope has the benefit that Var_Parse already allows for
    112  1.1  rillig # a custom scope to be passed as parameter.  This would have another side
    113  1.1  rillig # effect though.  There are several modifiers that actually modify variables,
    114  1.1  rillig # and these modifications happen in the scope that is passed to Var_Parse.
    115  1.1  rillig # This would mean that the combination of a .for variable and the modifiers
    116  1.1  rillig # '::=', '::+=', '::?=', '::!=' and ':_' would lead to different behavior than
    117  1.1  rillig # before.
    118  1.1  rillig 
    119  1.1  rillig # TODO: Add code that demonstrates the current interaction between variables
    120  1.1  rillig #  from .for loops and the modifiers mentioned above.
    121