directive-for-empty.mk revision 1.3 1 1.3 rillig # $NetBSD: directive-for-empty.mk,v 1.3 2023/11/19 21:47:52 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.1 rillig # named 'i', this expression 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.1 rillig . if empty(i)
35 1.2 rillig # expect+3: Missing argument for ".error"
36 1.2 rillig # expect+2: Missing argument for ".error"
37 1.2 rillig # expect+1: Missing argument for ".error"
38 1.1 rillig . error # due to the leaky abstraction
39 1.1 rillig . endif
40 1.1 rillig # The typical way of using 'empty' with variables from .for loops is pattern
41 1.1 rillig # matching using the modifiers ':M' or ':N'.
42 1.1 rillig . if !empty(i:M*2*)
43 1.1 rillig . if ${i} != "12"
44 1.1 rillig . error
45 1.1 rillig . endif
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