1 # $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 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 condition 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 . error # not reached, due to the leaky abstraction 36 . endif 37 # The typical way of mistakenly using 'empty' with variables from .for loops 38 # is pattern matching using the modifiers ':M' or ':N'. 39 . if !empty(i:M*2*) 40 . error 41 . endif 42 # Instead of the 'empty' function, the variables from .for loops can be 43 # queried using conditions of the form '${var:...} != ""'. 44 . if $i == "12" && ${i:M*2*} != "12" 45 . error 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 126 all: 127