macro: Allow macro A to use its arguments as part of call to macro B.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 22 Jul 2021 06:00:10 +0000 (23:00 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 23 Jul 2021 03:23:20 +0000 (20:23 -0700)
I overlooked this possibility before.  This implements it.

Thanks to Frans Houweling for reporting the issue.

doc/flow-control.texi
src/language/lexer/macro.c
tests/language/control/define.at

index 2a4bd0afae53537c3b402c39615c8526fbc8b3d8..c68923f8c052f35fe39cb3255daa2375e554a75b 100644 (file)
@@ -398,8 +398,9 @@ To expand macros in these cases, use the @code{!EVAL} macro function,
 e.g.@: @code{!LENGTH(!EVAL(!vars))} or @code{!LENGTH(!EVAL(!1))}.
 @xref{Macro Functions}, for details.
 
-These rules apply to macro calls, not to uses of macro functions and
-macro arguments within a macro body, which are always expanded.
+These rules apply to macro calls, not to uses within a macro body of
+macro functions, macro arguments, and macro variables created by
+@code{!DO} or @code{!LET}, which are always expanded.
 
 @node Macro Functions
 @subsection Macro Functions
@@ -797,6 +798,24 @@ SET MEXPAND, etc. doesn't work inside macro bodies.
 @node Macro Notes
 @subsection Additional Notes
 
+@subsubsection Calling Macros from Macros
+
+If the body of macro A includes a call to macro B, the call can use
+macro arguments (including @code{!*}) and macro variables as part of
+arguments to B.  For @code{!TOKENS} arguments, the argument or
+variable name counts as one token regardless of the number that
+expands into, for @code{!CHAREND} and @code{!ENCLOSE} arguments the
+delimiters come only from the call, not the expansions, and
+@code{!CMDEND} ends at the calling command, not any end of command
+within an argument or variable.
+
+Macro functions are not supported as part of the arguments in a macro
+call.  To get the same effect, use @code{!LET} to define a macro
+variable, then pass the macro variable to the macro.
+
+When macro A calls macro B, the order of their @code{DEFINE} commands
+doesn't matter, as long as macro B has been defined when A is called.
+
 @subsubsection Command Terminators
 
 Macros and command terminators require care.  Macros honor the syntax
index ab1e78efcf0e3b691c9a55ca1591182536e23175..d20b74c9fe20e9f2c8b6d8ec71078313d9ead6fd 100644 (file)
@@ -543,12 +543,17 @@ struct macro_call
     const struct macro *macro;
     struct macro_tokens **args;
     const struct macro_expansion_stack *stack;
+    const struct macro_expander *me;
 
     enum mc_state state;
     size_t n_tokens;
     const struct macro_param *param; /* Parameter currently being parsed. */
   };
 
+static bool macro_expand_arg (const struct token *,
+                              const struct macro_expander *,
+                              struct macro_tokens *exp);
+
 /* Completes macro expansion by initializing arguments that weren't supplied to
    their defaults. */
 static int
@@ -633,30 +638,29 @@ mc_add_arg (struct macro_call *mc, const struct macro_token *mt,
   struct macro_tokens **argp = &mc->args[p - mc->macro->params];
   if (!*argp)
     *argp = xzalloc (sizeof **argp);
-  struct macro_tokens *arg = *argp;
+
+  bool add_token;               /* Should we add 'mt' to the current arg? */
+  bool next_arg;                /* Should we advance to the next arg? */
   if (p->arg_type == ARG_N_TOKENS)
     {
-      macro_tokens_add (arg, mt);
-      if (arg->n >= p->n_tokens)
-        return mc_next_arg (mc);
-      return 0;
+      next_arg = (*argp)->n + 1 >= p->n_tokens;
+      add_token = true;
     }
-  else if (p->arg_type == ARG_CMDEND)
+  else
     {
-      if (token->type == T_ENDCMD || token->type == T_STOP)
-        return mc_next_arg (mc);
-      macro_tokens_add (arg, mt);
-      return 0;
+      next_arg = (p->arg_type == ARG_CMDEND
+                  ? token->type == T_ENDCMD || token->type == T_STOP
+                  : token_equal (token, (p->arg_type == ARG_CHAREND
+                                         ? &p->charend : &p->enclose[1])));
+      add_token = !next_arg;
     }
-  else
+
+  if (add_token)
     {
-      const struct token *end
-        = p->arg_type == ARG_CHAREND ? &p->charend : &p->enclose[1];
-      if (token_equal (token, end))
-        return mc_next_arg (mc);
-      macro_tokens_add (arg, mt);
-      return 0;
+      if (!macro_expand_arg (&mt->token, mc->me, *argp))
+        macro_tokens_add (*argp, mt);
     }
+  return next_arg ? mc_next_arg (mc) : 0;
 }
 
 static int
@@ -762,6 +766,7 @@ mc_equals (struct macro_call *mc, const struct macro_token *mt,
 static int
 macro_call_create__ (const struct macro_set *macros,
                      const struct macro_expansion_stack *stack,
+                     const struct macro_expander *me,
                      const struct token *token,
                      struct macro_call **mcp)
 {
@@ -786,6 +791,7 @@ macro_call_create__ (const struct macro_set *macros,
     .args = macro->n_params ? xcalloc (macro->n_params, sizeof *mc->args) : NULL,
     .param = macro->params,
     .stack = stack,
+    .me = me,
   };
   *mcp = mc;
 
@@ -804,7 +810,7 @@ macro_call_create (const struct macro_set *macros,
                    const struct token *token,
                    struct macro_call **mcp)
 {
-  return macro_call_create__ (macros, NULL, token, mcp);
+  return macro_call_create__ (macros, NULL, NULL, token, mcp);
 }
 
 void
@@ -1893,7 +1899,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
 }
 
 static void
-macro_expand_arg (const struct macro_expander *me, size_t idx,
+macro_expand_arg__ (const struct macro_expander *me, size_t idx,
                   struct macro_tokens *exp)
 {
   const struct macro_param *param = &me->macro->params[idx];
@@ -1925,6 +1931,44 @@ macro_expand_arg (const struct macro_expander *me, size_t idx,
       macro_tokens_add (exp, &arg->mts[i]);
 }
 
+static bool
+macro_expand_arg (const struct token *token, const struct macro_expander *me,
+                  struct macro_tokens *exp)
+{
+  if (!me || token->type != T_MACRO_ID)
+    return false;
+
+  /* Macro arguments. */
+  if (me->macro)
+    {
+      const struct macro_param *param = macro_find_parameter_by_name (
+        me->macro, token->string);
+      if (param)
+        {
+          macro_expand_arg__ (me, param - me->macro->params, exp);
+          return true;
+        }
+      else if (ss_equals (token->string, ss_cstr ("!*")))
+        {
+          for (size_t j = 0; j < me->macro->n_params; j++)
+            macro_expand_arg__ (me, j, exp);
+          return true;
+        }
+    }
+
+  /* Variables set by !DO or !LET. */
+  const char *var = stringi_map_find__ (me->vars, token->string.string,
+                                        token->string.length);
+  if (var)
+    {
+      macro_tokens_from_string__ (exp, ss_cstr (var),
+                                  me->segmenter_mode, me->stack);
+      return true;
+    }
+
+  return false;
+}
+
 static size_t
 macro_expand__ (const struct macro_token *mts, size_t n,
                 const struct macro_expander *me,
@@ -1936,7 +1980,8 @@ macro_expand__ (const struct macro_token *mts, size_t n,
   if (*me->expand)
     {
       struct macro_call *submc;
-      int n_call = macro_call_create__ (me->macros, me->stack, token, &submc);
+      int n_call = macro_call_create__ (me->macros, me->stack, me,
+                                        token, &submc);
       for (size_t j = 1; !n_call; j++)
         {
           const struct macro_token endcmd
@@ -1978,33 +2023,9 @@ macro_expand__ (const struct macro_token *mts, size_t n,
       return 1;
     }
 
-  /* Parameters. */
-  if (me->macro)
-    {
-      const struct macro_param *param = macro_find_parameter_by_name (
-        me->macro, token->string);
-      if (param)
-        {
-          macro_expand_arg (me, param - me->macro->params, exp);
-          return 1;
-        }
-      else if (ss_equals (token->string, ss_cstr ("!*")))
-        {
-          for (size_t j = 0; j < me->macro->n_params; j++)
-            macro_expand_arg (me, j, exp);
-          return 1;
-        }
-    }
-
-  /* Variables set by !DO or !LET. */
-  const char *var = stringi_map_find__ (me->vars, token->string.string,
-                                        token->string.length);
-  if (var)
-    {
-      macro_tokens_from_string__ (exp, ss_cstr (var),
-                                  me->segmenter_mode, me->stack);
-      return 1;
-    }
+  /* Parameters and macro variables. */
+  if (macro_expand_arg (token, me, exp))
+    return 1;
 
   /* Macro functions. */
   struct string function_output = DS_EMPTY_INITIALIZER;
index 9e1483ad82dab175ec3f339d221d58a18732f8c1..bad9c15cbcd49ec36ed02b43d3761558b8b13a62 100644 (file)
@@ -2058,4 +2058,20 @@ positive integer for N OF CASES.
 define.sps:17.12-17.22: error: N OF CASES: Syntax error at `!minus !one':
 Expected positive integer for N OF CASES.
 ])
+AT_CLEANUP
+
+AT_SETUP([one macro calls another])
+AT_DATA([define.sps], [dnl
+DEFINE !a(!pos !enclose('(',')')) [[!1]] !ENDDEFINE.
+DEFINE !b(!pos !enclose('{','}')) !a(x !1 z) !ENDDEFINE.
+DEFINE !c(!pos !enclose('{','}')) !let !tmp=!quote(!concat('<',!1,'>')) !a(!tmp) !ENDDEFINE.
+DEBUG EXPAND.
+!b{y}.
+!c{y}.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+[[x y z]]
+
+[[ < y > ]]
+])
 AT_CLEANUP
\ No newline at end of file