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