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.
This commit is contained in:
Jouke Witteveen 2020-11-01 22:48:53 +01:00 committed by Paul Smith
parent a8f4669b23
commit fcc11d05a6
4 changed files with 259 additions and 13 deletions

6
NEWS
View File

@ -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 <j.witteveen@gmail.com>
* New debug option "print" will show the recipe to be run, even when silent
mode is set.

View File

@ -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.@*

View File

@ -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),

110
tests/scripts/functions/let Normal file
View File

@ -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;