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