From 72d12e896c52ce226c43dce80f683204e46e4f05 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 23 Jul 2021 23:08:31 -0700 Subject: [PATCH] macro: Allow positional arguments to be empty. 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 | 17 +- src/language/lexer/macro.c | 86 ++++++-- tests/language/control/define.at | 344 ++++++++++++++++++++++--------- 3 files changed, 318 insertions(+), 129 deletions(-) diff --git a/doc/flow-control.texi b/doc/flow-control.texi index c68923f8c0..d3235612b9 100644 --- a/doc/flow-control.texi +++ b/doc/flow-control.texi @@ -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: diff --git a/src/language/lexer/macro.c b/src/language/lexer/macro.c index a6d08b917d..270cae50fa 100644 --- a/src/language/lexer/macro.c +++ b/src/language/lexer/macro.c @@ -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; diff --git a/tests/language/control/define.at b/tests/language/control/define.at index bad9c15cbc..876d650130 100644 --- a/tests/language/control/define.at +++ b/tests/language/control/define.at @@ -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){c}. +!p (a). +!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){c}. +!p (a){c. +!p (a){. +!p (a). +!p (a)" 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) ). -- 2.30.2