From 9632637a783f8d32ff4d76dca42e3d1bb6a8834b Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 19 Jul 2021 20:49:32 -0700 Subject: [PATCH] DEFINE: Allow !DEFAULT to follow the argument type declaration. Frans Houweling reported that SPSS allows either order. --- src/language/control/define.c | 150 +++++++++++++++++++------------ tests/language/control/define.at | 12 ++- 2 files changed, 105 insertions(+), 57 deletions(-) diff --git a/src/language/control/define.c b/src/language/control/define.c index 8f3bfa23aa..cda4c71fa6 100644 --- a/src/language/control/define.c +++ b/src/language/control/define.c @@ -77,6 +77,22 @@ parse_quoted_token (struct lexer *lexer, struct token *token) return true; } +static bool +dup_arg_type (struct lexer *lexer, bool *saw_arg_type) +{ + if (*saw_arg_type) + { + lex_error (lexer, _("Only one of !TOKENS, !CHAREND, !ENCLOSE, or " + "!CMDEND is allowed.")); + return false; + } + else + { + *saw_arg_type = true; + return true; + } +} + int cmd_define (struct lexer *lexer, struct dataset *ds UNUSED) { @@ -149,74 +165,98 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED) goto error; } - /* Parse default value. */ - if (match_macro_id (lexer, "!DEFAULT")) + bool saw_default = false; + bool saw_arg_type = false; + for (;;) { - if (!lex_force_match (lexer, T_LPAREN)) - goto error; - - /* XXX Should this handle balanced inner parentheses? */ - while (!lex_match (lexer, T_RPAREN)) + if (match_macro_id (lexer, "!DEFAULT")) { - if (lex_token (lexer) == T_ENDCMD) + if (saw_default) { - lex_error_expecting (lexer, ")"); + lex_error (lexer, + _("!DEFAULT is allowed only once per argument.")); goto error; } - char *syntax = lex_next_representation (lexer, 0, 0); - const struct macro_token mt = { - .token = *lex_next (lexer, 0), - .syntax = ss_cstr (syntax), - }; - macro_tokens_add (&p->def, &mt); - free (syntax); + saw_default = true; + + if (!lex_force_match (lexer, T_LPAREN)) + goto error; + /* XXX Should this handle balanced inner parentheses? */ + while (!lex_match (lexer, T_RPAREN)) + { + if (lex_token (lexer) == T_ENDCMD) + { + lex_error_expecting (lexer, ")"); + goto error; + } + char *syntax = lex_next_representation (lexer, 0, 0); + const struct macro_token mt = { + .token = *lex_next (lexer, 0), + .syntax = ss_cstr (syntax), + }; + macro_tokens_add (&p->def, &mt); + free (syntax); + + lex_get (lexer); + } + } + else if (match_macro_id (lexer, "!NOEXPAND")) + p->expand_arg = false; + else if (match_macro_id (lexer, "!TOKENS")) + { + if (!dup_arg_type (lexer, &saw_arg_type) + || !lex_force_match (lexer, T_LPAREN) + || !lex_force_int_range (lexer, "!TOKENS", 1, INT_MAX)) + goto error; + p->arg_type = ARG_N_TOKENS; + p->n_tokens = lex_integer (lexer); lex_get (lexer); + if (!lex_force_match (lexer, T_RPAREN)) + goto error; } - } + else if (match_macro_id (lexer, "!CHAREND")) + { + if (!dup_arg_type (lexer, &saw_arg_type)) + goto error; - if (match_macro_id (lexer, "!NOEXPAND")) - p->expand_arg = false; + p->arg_type = ARG_CHAREND; + p->charend = (struct token) { .type = T_STOP }; - if (match_macro_id (lexer, "!TOKENS")) - { - if (!lex_force_match (lexer, T_LPAREN) - || !lex_force_int_range (lexer, "!TOKENS", 1, INT_MAX)) - goto error; - p->arg_type = ARG_N_TOKENS; - p->n_tokens = lex_integer (lexer); - lex_get (lexer); - if (!lex_force_match (lexer, T_RPAREN)) - goto error; - } - else if (match_macro_id (lexer, "!CHAREND")) - { - p->arg_type = ARG_CHAREND; - p->charend = (struct token) { .type = T_STOP }; + if (!lex_force_match (lexer, T_LPAREN) + || !parse_quoted_token (lexer, &p->charend) + || !lex_force_match (lexer, T_RPAREN)) + goto error; + } + else if (match_macro_id (lexer, "!ENCLOSE")) + { + if (!dup_arg_type (lexer, &saw_arg_type)) + goto error; + + p->arg_type = ARG_ENCLOSE; + p->enclose[0] = p->enclose[1] = (struct token) { .type = T_STOP }; + + if (!lex_force_match (lexer, T_LPAREN) + || !parse_quoted_token (lexer, &p->enclose[0]) + || !lex_force_match (lexer, T_COMMA) + || !parse_quoted_token (lexer, &p->enclose[1]) + || !lex_force_match (lexer, T_RPAREN)) + goto error; + } + else if (match_macro_id (lexer, "!CMDEND")) + { + if (!dup_arg_type (lexer, &saw_arg_type)) + goto error; - if (!lex_force_match (lexer, T_LPAREN) - || !parse_quoted_token (lexer, &p->charend) - || !lex_force_match (lexer, T_RPAREN)) - goto error; - } - else if (match_macro_id (lexer, "!ENCLOSE")) - { - p->arg_type = ARG_ENCLOSE; - p->enclose[0] = p->enclose[1] = (struct token) { .type = T_STOP }; - - if (!lex_force_match (lexer, T_LPAREN) - || !parse_quoted_token (lexer, &p->enclose[0]) - || !lex_force_match (lexer, T_COMMA) - || !parse_quoted_token (lexer, &p->enclose[1]) - || !lex_force_match (lexer, T_RPAREN)) - goto error; + p->arg_type = ARG_CMDEND; + } + else + break; } - else if (match_macro_id (lexer, "!CMDEND")) - p->arg_type = ARG_CMDEND; - else + if (!saw_arg_type) { - lex_error_expecting (lexer, "!TOKENS", "!CHAREND", - "!ENCLOSE", "!CMDEND"); + lex_error_expecting (lexer, "!TOKENS", "!CHAREND", "!ENCLOSE", + "!CMDEND"); goto error; } diff --git a/tests/language/control/define.at b/tests/language/control/define.at index 3e8829b5e6..e0687677ec 100644 --- a/tests/language/control/define.at +++ b/tests/language/control/define.at @@ -367,7 +367,7 @@ note: unexpanded token "=" note: unexpanded token "y"]) PSPP_CHECK_MACRO_EXPANSION([default keyword arguments], - [DEFINE !k(arg1 = !DEFAULT(a b c) !CMDEND) k(!arg1) !ENDDEFINE], + [DEFINE !k(arg1 = !CMDEND !DEFAULT(a b c)) k(!arg1) !ENDDEFINE], [!k arg1=x. !k], [k(x) @@ -1634,6 +1634,8 @@ DEFINE !macro(x=!CHAREND(8)) !ENDDEFINE. DEFINE !macro(x=!CHAREND('x' 9)) !ENDDEFINE. DEFINE !macro(x=!WTF) !ENDDEFINE. DEFINE !macro(x=!TOKENS(1) x) !ENDDEFINE. +DEFINE !macro(x=!DEFAULT() !DEFAULT()) !ENDDEFINE. +DEFINE !macro(x=!TOKENS(1) !CMDEND) !ENDDEFINE. DEFINE !macro() ]) AT_CHECK([pspp define.sps], [1], [dnl @@ -1685,7 +1687,13 @@ TOKENS, !CHAREND, !ENCLOSE, or !CMDEND. define.sps:21.28: error: DEFINE: Syntax error at `x': expecting `/'. -define.sps:23.1: error: DEFINE: Syntax error at end of command: Expecting macro +define.sps:22.36: error: DEFINE: Syntax error at `(': !DEFAULT is allowed only +once per argument. + +define.sps:23.35: error: DEFINE: Syntax error at `)': Only one of !TOKENS, ! +CHAREND, !ENCLOSE, or !CMDEND is allowed. + +define.sps:25.1: error: DEFINE: Syntax error at end of command: Expecting macro body or !ENDDEFINE. ]) AT_CLEANUP -- 2.30.2