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