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 <petedi...@gmail.com>
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

Reply via email to