bug-make
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [RFC] Scoped variables, supercharged


From: Jouke Witteveen
Subject: Re: [RFC] Scoped variables, supercharged
Date: Fri, 20 Mar 2020 20:46:13 +0100

On Thu, Dec 26, 2019 at 2:03 PM Jouke Witteveen <address@hidden> wrote:
> I would like make to have scoped variables. Here, I will propose an
> implementation of them. This implementation is currently without tests and
> documentation. Hopefully, the proposal is acceptable and I can add the
> tests and documentation.

Was this proposal ever rejected? I think it may have gotten lost a bit
when dealing with the release of GNU Make 4.3.
Also, the reviews of my proposal seemed to have judged it based on
something it is not and does not try to be. All it does is a little
pattern matching and lexical scoping, basically reordering existing
$(foreach) implementation code.

As an extra example of how useful let expressions can be, consider the
following staple of functional programming:

# Right Fold
#  $1: Macro name
#  $2: List
Fold = $(let first rest,$2, \
    $(if $(rest), \
        $(call $1,$(first),$(call $0,$1,$(rest))), \
        $(first)))

Regards,
- Jouke

> Consider a situation in which we have macros F and G, and some variable X,
> and our makefile includes:
>
>   $(call F,$(call G,$(X)),$(call G,$(X)))
>
> Here, we duplicate the call to G. To make that more transparent (and
> assuming G does not introduce side-effects), we could write the above as:
>
>   Y := $(call G,$(X))
>   $(call F,$(Y),$(Y))
>   undefine Y
>
> However, this would interfere with any existing variable Y.
> Alternatively, we could try:
>
>   $(foreach Y,$(call G,$(X)), \
>     $(call F,$(Y),$(Y)))
>
> but that would not work if $(call G,$(X)) yields a list.
>
> A solution would be a new function, 'let', which allows us to write
>
>   $(let Y,$(call G,$(X)), \
>     $(call F,$(Y),$(Y)))
>
> This function can be implemented easily. It can even be given superpowers.
> If the first argument to the new let-function is a single name, it is
> assigned the full second argument. If it is multiple names, say n, we can
> assign the first n-1 names to the first n-1 words of the second argument
> and the final name to the remainder of the arguments (adding empty words
> as necessary).
>
> This also solves http://savannah.gnu.org/bugs/?51286 and makes something
> like
>
>   reverse = $(let first rest,$1,$(if $(rest),$(call reverse,$(rest)) 
> )$(first))
>
> possible.
>
> I have included an implementation bbelow, borrowing from the
> implementation of foreach and call. Let me know if I can go forward with
> this idea and prepare a patch including tests and documentation.
>
> Regards,
> - Jouke
>
> ---
>  src/function.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 50 insertions(+), 1 deletion(-)
>
> diff --git a/src/function.c b/src/function.c
> index 4ebff16..1c1c38b 100644
> --- a/src/function.c
> +++ b/src/function.c
> @@ -908,6 +908,53 @@ 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 *list_iterator = list;
> +  char *p;
> +  size_t len;
> +  size_t vlen;
> +  const char *vp_next = varnames;
> +  const char *vp = find_next_token (&vp_next, &vlen);
> +
> +  push_new_variable_scope ();
> +
> +  /* loop through LIST for all but the last VARNAME */
> +  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);
> +    }
> +  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 +2384,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 +2421,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),
> --
> 2.24.1
>



reply via email to

[Prev in Thread] Current Thread [Next in Thread]