macro: Allow positional arguments to be empty.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 24 Jul 2021 06:08:31 +0000 (23:08 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 24 Jul 2021 06:30:29 +0000 (23:30 -0700)
It wasn't clear before that this was allowed, but it seems that it should
be for compatibility.

Reported by Frans Houweling.

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

index c68923f8c052f35fe39cb3255daa2375e554a75b..d3235612b9fddb04ec0e42a1c697ae3e4afd0e17 100644 (file)
@@ -243,8 +243,8 @@ A @dfn{positional} argument has a required value that follows the
 macro's name.  Use the @code{!POSITIONAL} keyword to declare a
 positional argument.
 
-When a macro is called, every positional argument must be given a
-value in the same order as the defintion.
+When a macro is called, the positional argument values appear in the
+same order as their definitions, before any keyword argument values.
 
 References to a positional argument in a macro body are numbered:
 @code{!1} is the first positional argument, @code{!2} the second, and
@@ -266,10 +266,7 @@ FREQUENCIES /VARIABLES=!1.
 @item
 A @dfn{keyword} argument has a name.  In the macro call, its value is
 specified with the syntax @code{@i{name}=@i{value}}.  The names allow
-keyword argument values to take any order in the call, and even to be
-omitted.  When one is omitted, a default value is used: either the
-value specified in @code{!DEFAULT(@i{value})}, or an empty value
-otherwise.
+keyword argument values to take any order in the call.
 
 In declaration and calls, a keyword argument's name may not begin with
 @samp{!}, but references to it in the macro body do start with a
@@ -291,7 +288,13 @@ FREQUENCIES /VARIABLES=!vars.
 
 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.
+values also come first in macro calls.  A keyword argument may be
+omitted by leaving its keyword out of the call, and a positional
+argument may be omitted by putting a command terminator where it would
+appear.  (The latter case also omits any following positional
+arguments and all keyword arguments, if there are any.)  When an
+argument is omitted, a default value is used: either the value
+specified in @code{!DEFAULT(@i{value})}, or an empty value otherwise.
 
 Each argument declaration specifies the form of its value:
 
index a6d08b917d8c476ae72a1f9295e38f1abb675540..270cae50fabf9c586898eebad17e9adbad0e015e 100644 (file)
@@ -604,38 +604,74 @@ mc_add_arg (struct macro_call *mc, const struct macro_token *mt,
             const struct msg_location *loc)
 {
   const struct macro_param *p = mc->param;
+  struct macro_tokens **argp = &mc->args[p - mc->macro->params];
 
   const struct token *token = &mt->token;
-  if ((token->type == T_ENDCMD || token->type == T_STOP)
-      && p->arg_type != ARG_CMDEND)
+  if (token->type == T_ENDCMD || token->type == T_STOP)
     {
-      mc_error (mc, loc,
-                _("Unexpected end of command reading argument %s "
-                  "to macro %s."), mc->param->name, mc->macro->name);
+      if (*argp)
+        {
+          switch (p->arg_type)
+            {
+            case ARG_CMDEND:
+              /* This is OK, it's the expected way to end the argument. */
+              break;
 
-      mc->state = MC_ERROR;
-      return -1;
+            case ARG_N_TOKENS:
+              mc_error (mc, loc,
+                        ngettext (_("Reached end of command expecting %zu "
+                                    "more token in argument %s to macro %s."),
+                                  _("Reached end of command expecting %zu "
+                                    "more tokens in argument %s to macro %s."),
+                                  p->n_tokens - (*argp)->n),
+                        p->n_tokens - (*argp)->n, p->name, mc->macro->name);
+              break;
+
+            case ARG_CHAREND:
+            case ARG_ENCLOSE:
+              {
+                char *end = token_to_string (&p->end);
+                mc_error (mc, loc, _("Reached end of command expecting \"%s\" "
+                                     "in argument %s to macro %s."),
+                          end, p->name, mc->macro->name);
+                free (end);
+              }
+              break;
+            }
+        }
+
+      /* The end of a command ends the current argument, precludes any further
+         arguments, and is not itself part of the argument. */
+      return mc_finished (mc);
     }
 
   mc->n_tokens++;
 
-  struct macro_tokens **argp = &mc->args[p - mc->macro->params];
   if (!*argp)
     *argp = xzalloc (sizeof **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)
+  switch (p->arg_type)
     {
+    case ARG_N_TOKENS:
       next_arg = (*argp)->n + 1 >= p->n_tokens;
       add_token = true;
-    }
-  else
-    {
-      next_arg = (p->arg_type == ARG_CMDEND
-                  ? token->type == T_ENDCMD || token->type == T_STOP
-                  : token_equal (token, &p->end));
+      break;
+
+    case ARG_CHAREND:
+    case ARG_ENCLOSE:
+      next_arg = token_equal (token, &p->end);
       add_token = !next_arg;
+      break;
+
+    case ARG_CMDEND:
+      next_arg = false;
+      add_token = true;
+      break;
+
+    default:
+      NOT_REACHED ();
     }
 
   if (add_token)
@@ -668,16 +704,21 @@ static int
 mc_enclose (struct macro_call *mc, const struct macro_token *mt,
             const struct msg_location *loc)
 {
-  const struct token *token = &mt->token;
   mc->n_tokens++;
 
-  if (token_equal (&mc->param->start, token))
+  const struct token *token = &mt->token;
+  const struct macro_param *p = mc->param;
+  if (token_equal (&p->start, token))
     {
+      struct macro_tokens **argp = &mc->args[p - mc->macro->params];
+      *argp = xzalloc (sizeof **argp);
       mc->state = MC_ARG;
       return 0;
     }
-
-  return mc_expected (mc, mt, loc, &mc->param->start);
+  else if (p->positional && (token->type == T_ENDCMD || token->type == T_STOP))
+    return mc_finished (mc);
+  else
+    return mc_expected (mc, mt, loc, &p->start);
 }
 
 static const struct macro_param *
@@ -711,9 +752,8 @@ mc_keyword (struct macro_call *mc, const struct macro_token *mt,
                                                               token->string);
   if (p)
     {
-      size_t arg_index = p - mc->macro->params;
-      mc->param = p;
-      if (mc->args[arg_index])
+      struct macro_tokens **argp = &mc->args[p - mc->macro->params];
+      if (*argp)
         {
           mc_error (mc, loc,
                     _("Argument %s multiply specified in call to macro %s."),
@@ -722,6 +762,8 @@ mc_keyword (struct macro_call *mc, const struct macro_token *mt,
           return -1;
         }
 
+      *argp = xzalloc (sizeof **argp);
+      mc->param = p;
       mc->n_tokens++;
       mc->state = MC_EQUALS;
       return 0;
index bad9c15cbcd49ec36ed02b43d3761558b8b13a62..876d650130185e96b368c5da985747b5d9d36120 100644 (file)
@@ -169,7 +169,9 @@ e(a b)
 
 cmd(1 2 3 4)
 
-cmd2(5 6, 7)
+cmd2(5 6, )
+
+note: unexpanded token "7"
 
 "Three !TOKENS(1) arguments."
 
@@ -179,97 +181,275 @@ p(1, -2, -3) (1 -2 -3)
 ])
 AT_CLEANUP
 
-AT_SETUP([macro expansion with positional arguments - negative])
+AT_SETUP([macro call missing positional !TOKENS arguments])
+AT_KEYWORDS([TOKENS])
 AT_DATA([define.sps], [dnl
-DEFINE !title(!positional !tokens(1)) !1 !ENDDEFINE.
-DEFINE !p(!positional !tokens(1)
-         /!positional !tokens(1)
-        /!positional !tokens(1))
+DEFINE !p(!positional !tokens(1) !default(x)
+         /!positional !tokens(1) !default(y)
+        /!positional !tokens(1) !default(z))
 (!1, !2, !3)
 !ENDDEFINE.
-
-DEFINE !ce(!positional !charend('/')) ce(!1) !ENDDEFINE.
-
-DEFINE !enc1(!positional !enclose('{', '}')) enc1(!1) !ENDDEFINE.
 DEBUG EXPAND.
-!title "Too few tokens for !TOKENS."
+!p a b c.
 !p a b.
 !p a.
 !p.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+(a, b, c)
 
-!title "Missing charend delimiter."
-!ce a b c.
+(a, b, z)
 
-!title "Missing start delimiter."
-!enc1 a b c.
+(a, y, z)
 
-!title "Missing end delimiter."
-!enc1{a b c.
+(x, y, z)
+])
+AT_CLEANUP
+
+AT_SETUP([macro call incomplete positional !TOKENS arguments])
+AT_KEYWORDS([TOKENS])
+AT_DATA([define.sps], [dnl
+DEFINE !p(!positional !tokens(2) !default(x)
+         /!positional !tokens(2) !default(y)
+        /!positional !tokens(2) !default(z))
+(!1, !2, !3)
+!ENDDEFINE.
+DEBUG EXPAND.
+!p a1 a2 b1 b2 c1 c2.
+!p a1 a2 b1 b2 c1.
+!p a1 a2 b1 b2.
+!p a1 a2 b1.
+!p a1 a2.
+!p a1.
+!p.
 ])
 AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
-"Too few tokens for !TOKENS."
+(a1 a2, b1 b2, c1 c2)
 
-define.sps:13.7: error: DEBUG EXPAND: Unexpected end of command reading
-argument !3 to macro !p.
+define.sps:8.18: error: DEBUG EXPAND: Reached end of command expecting 1 more
+token in argument !3 to macro !p.
 
-note: unexpanded token "!p"
+(a1 a2, b1 b2, c1)
 
-note: unexpanded token "a"
+(a1 a2, b1 b2, z)
 
-note: unexpanded token "b"
+define.sps:10.12: error: DEBUG EXPAND: Reached end of command expecting 1 more
+token in argument !2 to macro !p.
+
+(a1 a2, b1, z)
+
+(a1 a2, y, z)
+
+define.sps:12.6: error: DEBUG EXPAND: Reached end of command expecting 1 more
+token in argument !1 to macro !p.
+
+(a1, y, z)
+
+(x, y, z)
+])
+AT_CLEANUP
+
+AT_SETUP([macro call empty positional !CHAREND arguments])
+AT_KEYWORDS([CHAREND])
+AT_DATA([define.sps], [dnl
+DEFINE !p(!positional !charend(',') !default(x)
+         /!positional !charend(';') !default(y)
+        /!positional !charend(':') !default(z))
+(!1, !2, !3)
+!ENDDEFINE.
+DEBUG EXPAND.
+!p a,b;c:.
+!p a,b;:.
+!p a,;c:.
+!p a,;:.
+!p,b;c:.
+!p,b;:.
+!p,;c:.
+!p,;:.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+(a, b, c)
+
+(a, b, )
+
+(a, , c)
+
+(a, , )
+
+(, b, c)
+
+(, b, )
+
+(, , c)
+
+(, , )
+])
+AT_CLEANUP
+
+AT_SETUP([macro call missing positional !CHAREND arguments])
+AT_DATA([define.sps], [dnl
+DEFINE !p(!positional !charend(',') !default(x)
+         /!positional !charend(';') !default(y)
+        /!positional !charend(':') !default(z))
+(!1, !2, !3)
+!ENDDEFINE.
+DEBUG EXPAND.
+!p a,b;c:.
+!p a,b;.
+!p a,;.
+!p ,b;.
+!p ,;.
+
+!p a,.
+!p ,.
+
+!p.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+(a, b, c)
+
+(a, b, z)
+
+(a, , z)
+
+(, b, z)
+
+(, , z)
+
+(a, y, z)
+
+(, y, z)
+
+(x, y, z)
+])
+AT_CLEANUP
+
+AT_SETUP([macro call incomplete positional !CHAREND arguments])
+AT_KEYWORDS([CHAREND])
+AT_DATA([define.sps], [dnl
+DEFINE !p(!positional !charend(',') !default(x)
+         /!positional !charend(';') !default(y)
+        /!positional !charend(':') !default(z))
+(!1, !2, !3)
+!ENDDEFINE.
+DEBUG EXPAND.
+!p a,b;c:.
+!p a,b;c.
+!p a,b;.
+!p a,b.
+!p a,.
+!p a.
+!p.
+])
+AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
+(a, b, c)
+
+define.sps:8.9: error: DEBUG EXPAND: Reached end of command expecting ":" in
+argument !3 to macro !p.
+
+(a, b, c)
 
-define.sps:14.5: error: DEBUG EXPAND: Unexpected end of command reading
+(a, b, z)
+
+define.sps:10.7: error: DEBUG EXPAND: Reached end of command expecting ";" in
 argument !2 to macro !p.
 
-note: unexpanded token "!p"
+(a, b, z)
 
-note: unexpanded token "a"
+(a, y, z)
 
-define.sps:15.3: error: DEBUG EXPAND: Unexpected end of command reading
+define.sps:12.5: error: DEBUG EXPAND: Reached end of command expecting "," in
 argument !1 to macro !p.
 
-note: unexpanded token "!p"
+(a, y, z)
 
-"Missing charend delimiter."
+(x, y, z)
+])
+AT_CLEANUP
 
-define.sps:18.10: error: DEBUG EXPAND: Unexpected end of command reading
-argument !1 to macro !ce.
+AT_SETUP([macro call missing positional !ENCLOSE arguments])
+AT_KEYWORDS([ENCLOSE])
+AT_DATA([define.sps], [dnl
+DEFINE !p(!positional !enclose('(',')') !default(x)
+         /!positional !enclose('<','>') !default(y)
+        /!positional !enclose('{','}') !default(z))
+(!1, !2, !3)
+!ENDDEFINE.
+DEBUG EXPAND.
+!p (a)<b>{c}.
+!p (a)<b>.
+!p (a).
+!p.
+])
+AT_CHECK([pspp --testing-mode define.sps], [0], [dnl
+(a, b, c)
 
-note: unexpanded token "!ce"
+(a, b, z)
 
-note: unexpanded token "a"
+(a, y, z)
 
-note: unexpanded token "b"
+(x, y, z)
+])
+AT_CLEANUP
 
-note: unexpanded token "c"
+AT_SETUP([macro call incomplete positional !ENCLOSE arguments])
+AT_KEYWORDS([ENCLOSE])
+AT_DATA([define.sps], [dnl
+DEFINE !p(!positional !enclose('(',')') !default(x)
+         /!positional !enclose('<','>') !default(y)
+        /!positional !enclose('{','}') !default(z))
+(!1, !2, !3)
+!ENDDEFINE.
+DEBUG EXPAND.
+!p (a)<b>{c}.
+!p (a)<b>{c.
+!p (a)<b>{.
+!p (a)<b>.
+!p (a)<b.
+!p (a)<.
+!p (a).
+!p (a.
+!p (.
+!p.
+])
+AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
+(a, b, c)
 
-"Missing start delimiter."
+define.sps:8.12: error: DEBUG EXPAND: Reached end of command expecting "}" in
+argument !3 to macro !p.
 
-define.sps:21.7: error: DEBUG EXPAND: Found `a' while expecting `{' reading
-argument !1 to macro !enc1.
+(a, b, c)
 
-note: unexpanded token "!enc1"
+define.sps:9.11: error: DEBUG EXPAND: Reached end of command expecting "}" in
+argument !3 to macro !p.
 
-note: unexpanded token "a"
+(a, b, )
 
-note: unexpanded token "b"
+(a, b, z)
 
-note: unexpanded token "c"
+define.sps:11.9: error: DEBUG EXPAND: Reached end of command expecting ">" in
+argument !2 to macro !p.
 
-"Missing end delimiter."
+(a, b, z)
 
-define.sps:24.12: error: DEBUG EXPAND: Unexpected end of command reading
-argument !1 to macro !enc1.
+define.sps:12.8: error: DEBUG EXPAND: Reached end of command expecting ">" in
+argument !2 to macro !p.
 
-note: unexpanded token "!enc1"
+(a, , z)
 
-note: unexpanded token "{"
+(a, y, z)
 
-note: unexpanded token "a"
+define.sps:14.6: error: DEBUG EXPAND: Reached end of command expecting ")" in
+argument !1 to macro !p.
 
-note: unexpanded token "b"
+(a, y, z)
 
-note: unexpanded token "c"
+define.sps:15.5: error: DEBUG EXPAND: Reached end of command expecting ")" in
+argument !1 to macro !p.
+
+(, y, z)
+
+(x, y, z)
 ])
 AT_CLEANUP
 
@@ -329,14 +509,10 @@ note: unexpanded token "!k"
 
 note: unexpanded token "arg1"
 
-define.sps:4.9: error: DEBUG EXPAND: Unexpected end of command reading argument
-!arg1 to macro !k.
+define.sps:4.9: error: DEBUG EXPAND: Reached end of command expecting 1 more
+token in argument !arg1 to macro !k.
 
-note: unexpanded token "!k"
-
-note: unexpanded token "arg1"
-
-note: unexpanded token "="
+k( )
 ])
 AT_CLEANUP
 
@@ -387,44 +563,20 @@ note: unexpanded token "!k"
 
 note: unexpanded token "arg1"
 
-define.sps:7.9: error: DEBUG EXPAND: Unexpected end of command reading argument
-!arg1 to macro !k.
-
-note: unexpanded token "!k"
-
-note: unexpanded token "arg1"
-
-note: unexpanded token "="
-
-define.sps:8.10: error: DEBUG EXPAND: Unexpected end of command reading
+define.sps:7.9: error: DEBUG EXPAND: Reached end of command expecting "/" in
 argument !arg1 to macro !k.
 
-note: unexpanded token "!k"
-
-note: unexpanded token "arg1"
+k(, )
 
-note: unexpanded token "="
+define.sps:8.10: error: DEBUG EXPAND: Reached end of command expecting "/" in
+argument !arg1 to macro !k.
 
-note: unexpanded token "x"
+k(x, )
 
-define.sps:9.18: error: DEBUG EXPAND: Unexpected end of command reading
+define.sps:9.18: error: DEBUG EXPAND: Reached end of command expecting "/" in
 argument !arg2 to macro !k.
 
-note: unexpanded token "!k"
-
-note: unexpanded token "arg1"
-
-note: unexpanded token "="
-
-note: unexpanded token "x"
-
-note: unexpanded token "/"
-
-note: unexpanded token "arg2"
-
-note: unexpanded token "="
-
-note: unexpanded token "y"
+k(x, y)
 ])
 AT_CLEANUP
 
@@ -499,18 +651,10 @@ note: unexpanded token "="
 
 note: unexpanded token "x"
 
-define.sps:9.11: error: DEBUG EXPAND: Unexpected end of command reading
+define.sps:9.11: error: DEBUG EXPAND: Reached end of command expecting "@:}@" in
 argument !arg1 to macro !k.
 
-note: unexpanded token "!k"
-
-note: unexpanded token "arg1"
-
-note: unexpanded token "="
-
-note: unexpanded token "@{:@"
-
-note: unexpanded token "x"
+k(x, )
 
 define.sps:10.17: error: DEBUG EXPAND: Found `.' while expecting `=' reading
 argument !arg2 to macro !k.
@@ -1370,14 +1514,14 @@ DEBUG EXPAND.
 AT_CHECK([pspp --testing-mode define.sps], [1], [dnl
 In the expansion of `!DO',
 define.sps:1-3: inside the expansion of `!for',
-define.sps:7.1-7.11: error: DEBUG EXPAND: !DO loop over list exceeded maximum
+define.sps:7.1-7.10: error: DEBUG EXPAND: !DO loop over list exceeded maximum
 number of iterations 2.  (Use SET MITERATE to change the limit.)
 
 ( (a) (b) ).
 
 In the expansion of `!DO',
 define.sps:1-3: inside the expansion of `!for',
-define.sps:8.1-8.24: error: DEBUG EXPAND: !DO loop over list exceeded maximum
+define.sps:8.1-8.23: error: DEBUG EXPAND: !DO loop over list exceeded maximum
 number of iterations 2.  (Use SET MITERATE to change the limit.)
 
 ( (foo) (bar) ).