* src/function.c: Introduce the 'let' built-in function * tests/scripts/functions/let: Tests for the 'let' built-in function ---
This replaces: > [PATCH 1/2] * src/function.c: Introduce the 'let' built-in function and goes together with: > [PATCH 2/2 V2] * doc/make.texi: Document the let function On Fri, Oct 23, 2020 at 3:14 PM Paul Smith <psm...@gnu.org> wrote: > Sorry for the delay. I have been super-busy (you'd think working from > home would give one _more_ time but it seems not). No problem. I am not in a hurry, but would love to see this land in the next release. > The thing most critically missing is a set of regression tests we can > use to ensure that the feature is working (including various corner > cases you would like to ensure), so that we can be sure it _keeps_ > working. Also I run these tests under valgrind and ASAN and similar to > check for memory leaks and other errors. The tests introduced here are mostly inspired by those of foreach. Regards, - Jouke src/function.c | 53 +++++++++++++++++++++- tests/scripts/functions/let | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tests/scripts/functions/let diff --git a/src/function.c b/src/function.c index 0917e0c..c4ff030 100644 --- a/src/function.c +++ b/src/function.c @@ -908,6 +908,55 @@ 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 *next; @@ -2337,7 +2386,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); @@ -2373,6 +2423,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 0000000..d3dbe1a --- /dev/null +++ b/tests/scripts/functions/let @@ -0,0 +1,89 @@ +# -*-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"; + + +# 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; -- 2.29.2