Proposition & PATCH: Add a "letrec" builtin function

Pete Dietl <[email protected]>
Newsgroups gmane.comp.gnu.make.bugs
Message-ID <CACB-c0EKBg0U-XZ2sfnD44M5cgXCYHyTV_PN_K7_vHfF3FHsJA@mail.gmail.com>
Howdy all.

I propose adding a "letrec" builtin to complement the "let" builtin.
The "let" builtin does not allow one to define inner functions or
mutually recursive definitions.

Here is an example Makefile using the new function:

print-var = $(info $$($1) = '$($(1))')

$(letrec \
         a,foo, \
         b,bar, \
         c,$(call double,~hello~), \
         double,$(1)$(1), \
    $(call print-var,a) \
    $(call print-var,b) \
    $(call print-var,c) \
    $(info 2 x $$(a) = '$(call double,$(a))') \
  )

.PHONY: all
all:
        @:


Variable names and their values are provided pairwise. "Letrec" strips
any leading whitespace from the expansion of each variable name
argument.

Here is a patch implementing the functionality:

From 57727f01bbb88e57b13eaed7d4693f8c18a8aa58 Mon Sep 17 00:00:00 2001
From: Pete Dietl <[email protected]>
Date: Wed, 25 Dec 2024 22:24:26 -0800
Subject: [PATCH] Add letrec builtin

---
 src/function.c | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/src/function.c b/src/function.c
index b4c38052..ed574b6e 100644
--- a/src/function.c
+++ b/src/function.c
@@ -952,6 +952,46 @@ func_let (char *o, char **argv, const char
*funcname UNUSED)
   return o + strlen (o);
 }

+static char *
+func_letrec (char *o, char **argv, const char *funcname)
+{
+  size_t num_args = 0;
+
+  while (argv[num_args])
+    num_args++;
+
+  if ((num_args % 2) != 1)
+    {
+      /* is not a multiple of 2 plus one */
+      OS (fatal, *expanding_var, "%s: Wrong number of arguments", funcname);
+    }
+
+  push_new_variable_scope ();
+
+  for (size_t i = 0; i < (num_args - 1); i += 2)
+    {
+      size_t vartok_len;
+      char *varnamearg = expand_argument (argv[i], NULL);
+      const char *varname = varnamearg;
+      char *value = argv[i + 1];
+      char *vartok = find_next_token (&varname, &vartok_len);
+
+      if (*vartok == '\0')
+        OS (fatal, *expanding_var, "%s: Empty variable name", funcname);
+      define_variable (vartok, vartok_len, value, o_automatic, 1);
+      free (varnamearg);
+    }
+
+  /* Expand the body in the context of the arguments, adding the result to
+     the variable buffer.  */
+
+  o = expand_string_buf (o, argv[num_args - 1], SIZE_MAX);
+
+  pop_variable_scope ();
+
+  return o + strlen (o);
+}
+
 struct a_word
 {
   struct a_word *chain;
@@ -2416,6 +2456,7 @@ static const struct function_table_entry
function_table_init[] =
   FT_ENTRY ("join",          2,  2,  1,  func_join),
   FT_ENTRY ("lastword",      0,  1,  1,  func_lastword),
   FT_ENTRY ("let",           3,  3,  0,  func_let),
+  FT_ENTRY ("letrec",        3,  0,  0,  func_letrec),
   FT_ENTRY ("notdir",        0,  1,  1,  func_notdir_suffix),
   FT_ENTRY ("or",            1,  0,  0,  func_or),
   FT_ENTRY ("origin",        0,  1,  1,  func_origin),
--
2.43.0

Please let me know your thoughts and suggestions.
Thanks,
Pete
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.