1*8d5c8e21SSimon J. Gerraty# $NetBSD: directive-for-empty.mk,v 1.4 2024/05/31 07:13:12 rillig Exp $ 2954401e6SSimon J. Gerraty# 3954401e6SSimon J. Gerraty# Tests for .for loops containing conditions of the form 'empty(var:...)'. 4954401e6SSimon J. Gerraty# 5d5e0a182SSimon J. Gerraty# When a .for loop is expanded, expressions in the body of the loop 6954401e6SSimon J. Gerraty# are replaced with expressions containing the variable values. This 7954401e6SSimon J. Gerraty# replacement is a bit naive but covers most of the practical cases. The one 8954401e6SSimon J. Gerraty# popular exception is the condition 'empty(var:Modifiers)', which does not 9d5e0a182SSimon J. Gerraty# look like an expression and is thus not replaced. 10954401e6SSimon J. Gerraty# 11954401e6SSimon J. Gerraty# See also: 12954401e6SSimon J. Gerraty# https://gnats.netbsd.org/43821 13954401e6SSimon J. Gerraty 14954401e6SSimon J. Gerraty 15954401e6SSimon J. Gerraty# In the body of the .for loop, the expression '${i:M*2*}' is replaced with 16954401e6SSimon J. Gerraty# '${:U11:M*2*}', '${:U12:M*2*}', '${:U13:M*2*}', one after another. This 17954401e6SSimon J. Gerraty# replacement creates the impression that .for variables were real variables, 18954401e6SSimon J. Gerraty# when in fact they aren't. 19954401e6SSimon J. Gerraty.for i in 11 12 13 20954401e6SSimon J. Gerraty. if ${i:M*2*} 21148ee845SSimon J. Gerraty# expect+1: 2 22954401e6SSimon J. Gerraty.info 2 23954401e6SSimon J. Gerraty. endif 24954401e6SSimon J. Gerraty.endfor 25954401e6SSimon J. Gerraty 26954401e6SSimon J. Gerraty 27d5e0a182SSimon J. Gerraty# In conditions, the function call to 'empty' does not look like an 28954401e6SSimon J. Gerraty# expression, therefore it is not replaced. Since there is no global variable 29*8d5c8e21SSimon J. Gerraty# named 'i', this condition makes for a leaky abstraction. If the .for 30954401e6SSimon J. Gerraty# variables were real variables, calling 'empty' would work on them as well. 31954401e6SSimon J. Gerraty.for i in 11 12 13 32954401e6SSimon J. Gerraty# Asking for an empty iteration variable does not make sense as the .for loop 33954401e6SSimon J. Gerraty# splits the iteration items into words, and such a word cannot be empty. 34*8d5c8e21SSimon J. Gerraty. if !empty(i) 35*8d5c8e21SSimon J. Gerraty. error # not reached, due to the leaky abstraction 36954401e6SSimon J. Gerraty. endif 37*8d5c8e21SSimon J. Gerraty# The typical way of mistakenly using 'empty' with variables from .for loops 38*8d5c8e21SSimon J. Gerraty# is pattern matching using the modifiers ':M' or ':N'. 39954401e6SSimon J. Gerraty. if !empty(i:M*2*) 40954401e6SSimon J. Gerraty. error 41954401e6SSimon J. Gerraty. endif 42*8d5c8e21SSimon J. Gerraty# Instead of the 'empty' function, the variables from .for loops can be 43*8d5c8e21SSimon J. Gerraty# queried using conditions of the form '${var:...} != ""'. 44*8d5c8e21SSimon J. Gerraty. if $i == "12" && ${i:M*2*} != "12" 45*8d5c8e21SSimon J. Gerraty. error 46954401e6SSimon J. Gerraty. endif 47954401e6SSimon J. Gerraty.endfor 48954401e6SSimon J. Gerraty 49954401e6SSimon J. Gerraty 50954401e6SSimon J. Gerraty# The idea of replacing every occurrences of 'empty(i' in the body of a .for 51954401e6SSimon J. Gerraty# loop would be naive and require many special cases, as there are many cases 52954401e6SSimon J. Gerraty# that need to be considered when deciding whether the token 'empty' is a 53954401e6SSimon J. Gerraty# function call or not, as demonstrated by the following examples. For 54d5e0a182SSimon J. Gerraty# expressions like '${i:Modifiers}', this is simpler as a single 55d5e0a182SSimon J. Gerraty# dollar almost always starts an expression. For counterexamples and 56954401e6SSimon J. Gerraty# edge cases, see directive-for-escape.mk. Adding another such tricky detail 57954401e6SSimon J. Gerraty# is out of the question. 58954401e6SSimon J. Gerraty.MAKEFLAGS: -df 59954401e6SSimon J. Gerraty.for i in value 60954401e6SSimon J. Gerraty# The identifier 'empty' can only be used in conditions such as .if, .ifdef or 61954401e6SSimon J. Gerraty# .elif. In other lines the string 'empty(' must be preserved. 62954401e6SSimon J. GerratyCPPFLAGS+= -Dmessage="empty(i)" 63954401e6SSimon J. Gerraty# There may be whitespace between 'empty' and '('. 64954401e6SSimon J. Gerraty.if ! empty (i) 65954401e6SSimon J. Gerraty. error 66954401e6SSimon J. Gerraty.endif 67954401e6SSimon J. Gerraty# Even in conditions, the string 'empty(' is not always a function call, it 68954401e6SSimon J. Gerraty# can occur in a string literal as well. 69954401e6SSimon J. Gerraty.if "empty\(i)" != "empty(i)" 70954401e6SSimon J. Gerraty. error 71954401e6SSimon J. Gerraty.endif 72954401e6SSimon J. Gerraty# In comments like 'empty(i)', the text must be preserved as well. 73954401e6SSimon J. Gerraty# 74954401e6SSimon J. Gerraty# Conditions, including function calls to 'empty', can not only occur in 75954401e6SSimon J. Gerraty# condition directives, they can also occur in the modifier ':?', see 76954401e6SSimon J. Gerraty# varmod-ifelse.mk. 77954401e6SSimon J. GerratyCPPFLAGS+= -Dmacro="${empty(i):?empty:not-empty}" 78954401e6SSimon J. Gerraty.endfor 79954401e6SSimon J. Gerraty.MAKEFLAGS: -d0 80954401e6SSimon J. Gerraty 81954401e6SSimon J. Gerraty 82954401e6SSimon J. Gerraty# An idea to work around the above problems is to collect the variables from 83954401e6SSimon J. Gerraty# the .for loops in a separate scope. To match the current behavior, there 84954401e6SSimon J. Gerraty# has to be one scope per included file. There may be .for loops using the 85954401e6SSimon J. Gerraty# same variable name in files that include each other: 86954401e6SSimon J. Gerraty# 87954401e6SSimon J. Gerraty# outer.mk: .for i in outer 88954401e6SSimon J. Gerraty# . info $i # outer 89954401e6SSimon J. Gerraty# . include "inner.mk" 90954401e6SSimon J. Gerraty# inner.mk: . info $i # (undefined) 91954401e6SSimon J. Gerraty# . for i in inner 92954401e6SSimon J. Gerraty# . info $i # inner 93954401e6SSimon J. Gerraty# . endfor 94954401e6SSimon J. Gerraty# . info $i # (undefined) 95954401e6SSimon J. Gerraty# outer.mk: . info $i # outer 96954401e6SSimon J. Gerraty# .endfor 97954401e6SSimon J. Gerraty# 98954401e6SSimon J. Gerraty# This might be regarded another leaky abstraction, but it is in fact useful 99954401e6SSimon J. Gerraty# that variables from .for loops can only affect expressions in the current 100954401e6SSimon J. Gerraty# file. If variables from .for loops were implemented as global variables, 101954401e6SSimon J. Gerraty# they might interact between files. 102954401e6SSimon J. Gerraty# 103954401e6SSimon J. Gerraty# To emulate this exact behavior for the function 'empty', each file in the 104954401e6SSimon J. Gerraty# stack of included files needs its own scope that is independent from the 105954401e6SSimon J. Gerraty# other files. 106954401e6SSimon J. Gerraty# 107954401e6SSimon J. Gerraty# Another tricky detail are nested .for loops in a single file that use the 108954401e6SSimon J. Gerraty# same variable name. These are generally avoided by developers, as they 109954401e6SSimon J. Gerraty# would be difficult to understand for humans as well. Technically, they are 110954401e6SSimon J. Gerraty# possible though. Assuming there are two nested .for loops, both using the 111954401e6SSimon J. Gerraty# variable 'i'. When the inner .for loop ends, the inner 'i' needs to be 112954401e6SSimon J. Gerraty# removed from the scope, which would need to make the outer 'i' visible 113954401e6SSimon J. Gerraty# again. This would suggest to use one variable scope per .for loop. 114954401e6SSimon J. Gerraty# 115954401e6SSimon J. Gerraty# Using a separate scope has the benefit that Var_Parse already allows for 116954401e6SSimon J. Gerraty# a custom scope to be passed as parameter. This would have another side 117954401e6SSimon J. Gerraty# effect though. There are several modifiers that actually modify variables, 118954401e6SSimon J. Gerraty# and these modifications happen in the scope that is passed to Var_Parse. 119954401e6SSimon J. Gerraty# This would mean that the combination of a .for variable and the modifiers 120954401e6SSimon J. Gerraty# '::=', '::+=', '::?=', '::!=' and ':_' would lead to different behavior than 121954401e6SSimon J. Gerraty# before. 122954401e6SSimon J. Gerraty 123954401e6SSimon J. Gerraty# TODO: Add code that demonstrates the current interaction between variables 124954401e6SSimon J. Gerraty# from .for loops and the modifiers mentioned above. 125*8d5c8e21SSimon J. Gerraty 126*8d5c8e21SSimon J. Gerratyall: 127