!*
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 12 Jun 2021 19:09:33 +0000 (12:09 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 12 Jun 2021 19:09:33 +0000 (12:09 -0700)
doc/flow-control.texi
src/language/lexer/macro.c
tests/language/control/define.at

index f0bf4fe10b77400ed999dd56672f9c1b84f93460..056ffb31310753dddafabbebf29ad12fe165f4f0 100644 (file)
@@ -165,7 +165,8 @@ positional argument.
 
 References to a positional argument in a macro body are numbered:
 @code{!1} is the first positional argument, @code{!2} the second, and
-so on.
+so on.  In addition, @code{!*} expands to all of the positional
+argument values, separated by a space.
 
 The following example uses a positional argument:
 
@@ -205,15 +206,6 @@ FREQUENCIES /VARIABLES=!vars.
 @end example
 @end itemize
 
-@example
-DEFINE !analyze_kw(vars=!CMDEND)
-DESCRIPTIVES !vars.
-FREQUENCIES /VARIABLES=!vars.
-!ENDDEFINE.
-
-!analyze_kw vars=v1 v2 v3.
-@end example
-
 If a macro has both positional and keyword arguments, then the
 positional arguments must come first in the DEFINE command, and their
 values also come first in macro calls.
@@ -243,6 +235,18 @@ Any number of tokens up to @var{token}, which should be an operator or
 punctuator token such as @samp{/} or @samp{+}.  The @var{token} does
 not become part of the value.
 
+With the following variant of @code{!analyze_kw}, the variables must
+be following by @samp{/}:
+
+@example
+DEFINE !analyze_parens(vars=!CHARNED('/'))
+DESCRIPTIVES !vars.
+FREQUENCIES /VARIABLES=!vars.
+!ENDDEFINE.
+
+!analyze_parens vars=v1 v2 v3/.
+@end example
+
 @item !ENCLOSE('@var{start}','@var{end}')
 Any number of tokens enclosed between @var{start} and @var{end}, which
 should each be operator or punctuator tokens.  For example, use
@@ -267,12 +271,24 @@ FREQUENCIES /VARIABLES=!vars.
 Any number of tokens up to the end of the command.  This should be
 used only for the last positional parameter, since it consumes all of
 the tokens in the command calling the macro.
+
+The following variant of @code{!analyze_kw} takes all the variable
+names up to the end of the command as its argument:
+
+@example
+DEFINE !analyze_kw(vars=!CMDEND)
+DESCRIPTIVES !vars.
+FREQUENCIES /VARIABLES=!vars.
+!ENDDEFINE.
+
+!analyze_kw vars=v1 v2 v3.
+@end example
 @end table
 
 By default, when an argument's value contains a macro call, the call
 is expanded each time the argument appears in the macro's body.  The
 @code{!NOEXPAND} keyword in an argument declaration suppresses this
-expansion.
+expansion.  @xref{Controlling Macro Expansion}, for details.
 
 @node Controlling Macro Expansion
 @subsection Controlling Macro Expansion
index e900a5e7d1a542f0ec1faedaa813b0bd9ac883dc..b120b35d725e432fecbb96f5f503ee9031cc0b2f 100644 (file)
@@ -721,6 +721,17 @@ static bool
 expand_macro_function (struct parse_macro_function_ctx *ctx,
                        struct string *output, size_t *input_consumed);
 
+/* Returns true if the pair of tokens starting at offset OFS within MTS are !*,
+   false otherwise. */
+static bool
+is_bang_star (const struct macro_token *mts, size_t n, size_t ofs)
+{
+  return (ofs + 1 < n
+          && mts[ofs].token.type == T_MACRO_ID
+          && ss_equals (mts[ofs].token.string, ss_cstr ("!"))
+          && mts[ofs + 1].token.type == T_ASTERISK);
+}
+
 static size_t
 parse_function_arg (struct parse_macro_function_ctx *ctx,
                     size_t i, struct string *farg)
@@ -744,6 +755,24 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
           return 1;
         }
 
+      if (is_bang_star (ctx->input, ctx->n_input, i))
+        {
+          for (size_t i = 0; i < ctx->me->macro->n_params; i++)
+            {
+              if (!ctx->me->macro->params[i].positional)
+                break;
+
+              const struct macro_tokens *marg = ctx->me->args[i];
+              for (size_t j = 0; j < marg->n; j++)
+                {
+                  if (i || j)
+                    ds_put_byte (farg, ' ');
+                  ds_put_substring (farg, marg->mts[j].representation);
+                }
+            }
+          return 2;
+        }
+
       struct parse_macro_function_ctx subctx = {
         .input = &ctx->input[i],
         .n_input = ctx->n_input - i,
@@ -1056,6 +1085,25 @@ macro_expand (const struct macro_tokens *mts,
                   macro_tokens_add (exp, &arg->mts[i]);
               continue;
             }
+
+          if (is_bang_star (mts->mts, mts->n, i))
+            {
+              for (size_t j = 0; j < me->macro->n_params; j++)
+                {
+                  const struct macro_param *param = &me->macro->params[j];
+                  if (!param->positional)
+                    break;
+
+                  const struct macro_tokens *arg = me->args[j];
+                  if (*expand && param->expand_arg)
+                    macro_expand (arg, nesting_countdown, macros, NULL, expand, exp);
+                  else
+                    for (size_t k = 0; k < arg->n; k++)
+                      macro_tokens_add (exp, &arg->mts[k]);
+                }
+              i++;
+              continue;
+            }
         }
 
       if (*expand)
index bf5e2c4ae11dbaa0cabe39e9960bc1d83246e9aa..bbf3ba26fc5d85478393b97c1033d8d4c467a0c6 100644 (file)
@@ -87,7 +87,7 @@ cmd2(!1, !2)
 DEFINE !p(!positional !tokens(1)
          /!positional !tokens(1)
         /!positional !tokens(1))
-p(!1, !2, !3)
+p(!1, !2, !3)(!*)
 !ENDDEFINE.
 
 DEBUG EXPAND.
@@ -175,9 +175,9 @@ cmd2(5 6, 7)
 
 "Three !TOKENS(1) arguments."
 
-p(a, b, c)
+p(a, b, c) (a b c)
 
-p(1, -2, -3)
+p(1, -2, -3) (1 -2 -3)
 ])
 AT_CLEANUP
 \f
@@ -533,3 +533,20 @@ FRECKLE.
 A B C.
 A B C.])
 
+
+dnl !* is implemented separately inside and outside function arguments
+dnl so this test makes sure to include both.
+PSPP_CHECK_MACRO_EXPANSION([!*], [dnl
+DEFINE !m(!POSITIONAL !TOKENS(1)
+         /!POSITIONAL !TOKENS(1))
+!*/
+!LENGTH(!*)/
+!SUBSTR(!*, 3)/
+!QUOTE(!*).
+!ENDDEFINE.],
+  [!m 123 b
+!m 2 3
+!m '' 'b'.
+], [123 b / 5 / 3 b / '123 b'.
+2 3 / 3 / 3 / '2 3'.
+'' 'b' / 6 / 'b' / ''''' ''b'''.])
\ No newline at end of file