From: Jouke Witteveen Date: Sun, 1 Nov 2020 21:48:53 +0000 (+0100) Subject: Create $(let ...) providing lexically scoped variables X-Git-Tag: 4.3.90~192 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fcc11d05a60b061027a50b76d146c43306b20e32;p=thirdparty%2Fmake.git Create $(let ...) providing lexically scoped variables Add a new function $(let ...) which allows lexically scoped variables. * NEWS: Add information on this feature. * doc/make.texi (Let Function): Document the 'let' function. * src/function.c (func_let): Create the 'let' built-in function. * tests/scripts/functions/let: Test the 'let' built-in function. --- diff --git a/NEWS b/NEWS index 6802a7ce..5d71488c 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,12 @@ https://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=109&se https://www.gnu.org/software/gnulib/manual/html_node/C99-features-assumed.html The configure script should verify the compiler has these features. +* New feature: The $(let ...) function + This function allows user-defined functions to provide a lexically-scoped + set of variables: values can be assigned to these variables from within the + user-defined function and they will not impact global variable assignments. + Implementation provided by Jouke Witteveen + * New debug option "print" will show the recipe to be run, even when silent mode is set. diff --git a/doc/make.texi b/doc/make.texi index 62103efb..b9588124 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -276,6 +276,7 @@ Functions for Transforming Text * Text Functions:: General-purpose text manipulation functions. * File Name Functions:: Functions for manipulating file names. * Conditional Functions:: Functions that implement conditions. +* Let Function:: Lexically scoped variables. * Foreach Function:: Repeat some text with controlled variation. * File Function:: Write text to a file. * Call Function:: Expand a user-defined function. @@ -7038,6 +7039,7 @@ be substituted. * Text Functions:: General-purpose text manipulation functions. * File Name Functions:: Functions for manipulating file names. * Conditional Functions:: Functions that implement conditions. +* Let Function:: Lexically scoped variables. * Foreach Function:: Repeat some text with controlled variation. * File Function:: Write text to a file. * Call Function:: Expand a user-defined function. @@ -7638,7 +7640,7 @@ the file names to refer to an existing file or directory. Use the @code{wildcard} function to test for existence. @end table -@node Conditional Functions, Foreach Function, File Name Functions, Functions +@node Conditional Functions, Let Function, File Name Functions, Functions @section Functions for Conditionals @findex if @cindex conditional expansion @@ -7691,14 +7693,84 @@ the result of the expansion is the expansion of the last argument. @end table -@node Foreach Function, File Function, Conditional Functions, Functions +@node Let Function, Foreach Function, Conditional Functions, Functions +@section The @code{let} Function +@findex let +@cindex variables, lexically scoped + +The @code{let} function provides a means to limit the scope of a +variable. The assignment of the named variables in a @code{let} +expression is in effect only within the text provided by the +@code{let} expression, and this assignment doesn't impact that named +variable in any outer scope. + +Additionally, the @code{let} function enables list unpacking by +assigning all unassigned values to the last named variable. + +The syntax of the @code{let} function is: + +@example +$(let @var{var} [@var{var} ...],[@var{list}],@var{text}) +@end example + +@noindent +The first two arguments, @var{var} and @var{list}, are expanded before +anything else is done; note that the last argument, @var{text}, is +@strong{not} expanded at the same time. Next, each word of the +expanded value of @var{list} is bound to each of the variable names, +@var{var}, in turn, with the final variable name being bound to the +remainder of the expanded @var{list}. In other words, the first word +of @var{list} is bound to the first variable @var{var}, the second +word to the second variable @var{var}, and so on. + +If there are more variable names in @var{var} than there are words in +@var{list}, the remaining @var{var} variable names are set to the +empty string. If there are fewer @var{var}s than words in @var{list} +then the last @var{var} is set to all remaining words in @var{list}. + +The variables in @var{var} are assigned as simply-expanded variables +during the execution of @code{let}. @xref{Flavors, ,The Two Flavors +of Variables}.@refill + +After all variables are thus bound, @var{text} is expanded to provide +the result of the @code{let} function. + +For example, this macro reverses the order of the words in the list +that it is given as its first argument: + +@example +reverse = $(let first rest,$1,$(if $(rest),$(call reverse,$(rest)) )$(first)) + +all: ; @@echo $(call reverse,d c b a) +@end example + +@noindent +will print @code{a b c d}. When first called, @code{let} will expand +@var{$1} to @code{d c b a}. It will then assign @var{first} to +@code{d} and assign @var{rest} to @code{c b a}. It will then expand +the if-statement, where @code{$(rest)} is not empty so we recursively +invoke the @var{reverse} function with the value of @var{rest} which +is now @code{c b a}. The recursive invocation of @code{let} assigns +@var{first} to @code{c} and @var{rest} to @code{b a}. The recursion +continues until @code{let} is called with just a single value, +@code{a}. Here @var{first} is @code{a} and @var{rest} is empty, so we +do not recurse but simply expand @code{$(first)} to @code{a} and +return, which adds @code{ b}, etc. + +After the @var{reverse} call is complete, the @var{first} and +@var{rest} variables are no longer set. If variables by those names +existed beforehand, they are not affected by the expansion of the +@code{reverse} macro. + +@node Foreach Function, File Function, Let Function, Functions @section The @code{foreach} Function @findex foreach @cindex words, iterating over -The @code{foreach} function is very different from other functions. It -causes one piece of text to be used repeatedly, each time with a different -substitution performed on it. It resembles the @code{for} command in the +The @code{foreach} function is similar to the @code{let} function, but very +different from other functions. It causes one piece of text to be used +repeatedly, each time with a different substitution performed on it. The +@code{foreach} function resembles the @code{for} command in the shell @code{sh} and the @code{foreach} command in the C-shell @code{csh}. The syntax of the @code{foreach} function is: @@ -7757,13 +7829,14 @@ actual function call to be re-expanded under the control of @code{foreach}; a simply-expanded variable would not do, since @code{wildcard} would be called only once at the time of defining @code{find_files}. -The @code{foreach} function has no permanent effect on the variable -@var{var}; its value and flavor after the @code{foreach} function call are -the same as they were beforehand. The other values which are taken from -@var{list} are in effect only temporarily, during the execution of -@code{foreach}. The variable @var{var} is a simply-expanded variable -during the execution of @code{foreach}. If @var{var} was undefined -before the @code{foreach} function call, it is undefined after the call. +Like the @code{let} function, the @code{foreach} function has no permanent +effect on the variable @var{var}; its value and flavor after the +@code{foreach} function call are the same as they were beforehand. The +other values which are taken from @var{list} are in effect only +temporarily, during the execution of @code{foreach}. The variable +@var{var} is a simply-expanded variable during the execution of +@code{foreach}. If @var{var} was undefined before the @code{foreach} +function call, it is undefined after the call. @xref{Flavors, ,The Two Flavors of Variables}.@refill You must take care when using complex variable expressions that result in @@ -12409,6 +12482,11 @@ Return a string describing the flavor of the @code{make} variable @var{variable}.@* @xref{Flavor Function, , The @code{flavor} Function}. +@item $(let @var{var} [@var{var} ...],@var{words},@var{text}) +Evaluate @var{text} with the @var{var}s bound to the words in +@var{words}.@* +@xref{Let Function, ,The @code{let} Function}. + @item $(foreach @var{var},@var{words},@var{text}) Evaluate @var{text} with @var{var} bound to each word in @var{words}, and concatenate the results.@* diff --git a/src/function.c b/src/function.c index 87f2a8b3..d36b7951 100644 --- a/src/function.c +++ b/src/function.c @@ -908,6 +908,56 @@ func_foreach (char *o, char **argv, const char *funcname UNUSED) return o; } +static char * +func_let (char *o, char **argv, const char *funcname UNUSED) +{ + /* expand only the first two. */ + char *varnames = expand_argument (argv[0], NULL); + char *list = expand_argument (argv[1], NULL); + const char *body = argv[2]; + + const char *vp; + const char *vp_next = varnames; + const char *list_iterator = list; + char *p; + size_t len; + size_t vlen; + + push_new_variable_scope (); + + /* loop through LIST for all but the last VARNAME */ + vp = find_next_token (&vp_next, &vlen); + NEXT_TOKEN (vp_next); + while (*vp_next != '\0') + { + p = find_next_token (&list_iterator, &len); + if (*list_iterator != '\0') + { + ++list_iterator; + p[len] = '\0'; + } + define_variable (vp, vlen, p ? p : "", o_automatic, 0); + + vp = find_next_token (&vp_next, &vlen); + NEXT_TOKEN (vp_next); + } + + /* set the last VARNAME to the remainder of LIST */ + if (vp) + define_variable (vp, vlen, next_token (list_iterator), o_automatic, 0); + + /* Expand the body in the context of the arguments, adding the result to + the variable buffer. */ + + o = variable_expand_string (o, body, SIZE_MAX); + + pop_variable_scope (); + free (varnames); + free (list); + + return o + strlen (o); +} + struct a_word { struct a_word *chain; @@ -2344,7 +2394,8 @@ func_abspath (char *o, char **argv, const char *funcname UNUSED) comma-separated values are treated as arguments. EXPAND_ARGS means that all arguments should be expanded before invocation. - Functions that do namespace tricks (foreach) don't automatically expand. */ + Functions that do namespace tricks (foreach, let) don't automatically + expand. */ static char *func_call (char *o, char **argv, const char *funcname); @@ -2380,6 +2431,7 @@ static struct function_table_entry function_table_init[] = FT_ENTRY ("words", 0, 1, 1, func_words), FT_ENTRY ("origin", 0, 1, 1, func_origin), FT_ENTRY ("foreach", 3, 3, 0, func_foreach), + FT_ENTRY ("let", 3, 3, 0, func_let), FT_ENTRY ("call", 1, 0, 1, func_call), FT_ENTRY ("info", 0, 1, 1, func_error), FT_ENTRY ("error", 0, 1, 1, func_error), diff --git a/tests/scripts/functions/let b/tests/scripts/functions/let new file mode 100644 index 00000000..f1012e56 --- /dev/null +++ b/tests/scripts/functions/let @@ -0,0 +1,110 @@ +# -*-perl-*- +# $Id$ + +$description = "Test the let function."; + +$details = "This is a test of the let function in gnu make. +This function destructures a list of values and binds each +value to a variable name in a list of variable names. +Superfluous variable names are assigned the empty string and +the remaining values are assigned to the last variable name. +The binding holds for the duration of the evaluation of the +given text and no longer. The general form of the command +is $(let \$vars,\$list,\$text). Several types of let +assignments are tested\n"; + +# check for mismatched var and list word counts +run_make_test(q! +a = bad +b = news +x = $(let a b,1 2,$a $b) +y = $(let a,1 2,$a) +z = $(let a b,1,$a $b) +all:;@echo 'a=,$a,' 'b=,$b,' 'x=,$x,' 'y=,$y,' 'z=,$z,' +!, + '', "a=,bad, b=,news, x=,1 2, y=,1 2, z=,1 ,\n"); + +# check for whitespace +run_make_test(q! +a = bad +b = news +x = $(let a b, 1 2 ,+$a+$b+) +y = $(let a, 1 2 ,+$a+) +z = $(let a b, 1 ,+$a+$b+) +all:;@echo 'a=,$a,' 'b=,$b,' 'x=,$x,' 'y=,$y,' 'z=,$z,' +!, + '', "a=,bad, b=,news, x=,+1+2 +, y=,+1 2 +, z=,+1++,\n"); + +# Allow empty variable names and empty value list. +# We still expand the list and body. +run_make_test(' +null = +x = $(let $(null),$(info side-effect),abc) +y = $(let y,,$ydef) + +all: ; @echo $x$y', + '', "side-effect\nabcdef\n"); + +# The example macro from the manual. +run_make_test(' +reverse = $(let first rest,$1,$(if $(rest),$(call reverse,$(rest)) )$(first)) + +all: ; @echo $(call reverse, \ + moe miny meeny eeny \ + )', + '', "eeny meeny miny moe\n"); + + +# Set an environment variable that we can test in the makefile. +$ENV{FOOFOO} = 'foo foo'; + +# Verify masking: expansion outside the scope of let is unaffected. +run_make_test(' +auto_var = \ + udef \ + CC \ + FOOFOO \ + MAKE \ + foo \ + CFLAGS \ + WHITE \ + @ \ + < +av = $(foreach var, $(auto_var), $(origin $(var)) ) +foo = bletch null @ garf +override WHITE := BLACK + +define mktarget +target: foo := $(foo) +target: ; @echo $(AR)_$(foo)_ +endef + +all: auto target +auto: ; @echo $(let $(auto_var),,$(av)) $(av) +$(let AR foo,bar foo ,$(eval $(value mktarget)))', + '-e WHITE=WHITE CFLAGS=', + "automatic automatic automatic automatic automatic automatic automatic automatic automatic undefined default environment default file command line override automatic automatic +ar_foo _ +"); + + +# Check some error conditions. +run_make_test(' +x = $(let ) +y = $x + +all: ; @echo $y', + '', + "#MAKEFILE#:2: *** insufficient number of arguments (1) to function 'let'. Stop.", + 512); + +run_make_test(' +x = $(let x,y) +y := $x + +all: ; @echo $y', + '', + "#MAKEFILE#:2: *** insufficient number of arguments (2) to function 'let'. Stop.", + 512); + +1;