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