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