From: Ben Pfaff Date: Sat, 26 Jun 2021 21:55:29 +0000 (-0700) Subject: tons of progress on macros X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5dd9df91d08bb0283bde1b82266a29f6e5e17bb3;hp=b707aef4702e0998353de081458b7720f67a1b72;p=pspp tons of progress on macros --- diff --git a/doc/flow-control.texi b/doc/flow-control.texi index 20644747db..a3f39b6ccb 100644 --- a/doc/flow-control.texi +++ b/doc/flow-control.texi @@ -45,94 +45,142 @@ BREAK. @vindex DEFINE @cindex macro +@subsection Overview + @display -DEFINE macro_name([argument[/argument]@dots{}]) -@dots{}body@dots{} -!ENDDEFINE. +@t{DEFINE} @i{macro_name}@t{(}@r{[}@i{argument}@r{[}@t{/}@i{argument}@r{]@dots{}]}@t{)} +@dots{}@i{body}@dots{} +@t{!ENDDEFINE.} +@end display -Each argument takes the following form: - @{!arg_name =,!POSITIONAL@} [!DEFAULT(default)] [!NOEXPAND] - @{!TOKENS(count),!CHAREND('token'),!ENCLOSE('start','end'),!CMDEND@} +Each @i{argument} takes the following form: +@display +@r{@{}@i{!arg_name} @t{=},@t{!POSITIONAL}@r{@}} +@r{[}@t{!DEFAULT(}@i{default}@t{)}@r{]} +@r{[}@t{!NOEXPAND}@r{]} +@r{@{}@t{!TOKENS(}@i{count}@t{)},@t{!CHAREND('}@i{token}@t{')},@t{!ENCLOSE('}@i{start}@t{','}@i{end}@t{')},@t{!CMDEND}@} +@end display -The following directives may be used within the body: - !OFFEXPAND - !ONEXPAND +The following directives may be used within @i{body}: +@example +!OFFEXPAND +!ONEXPAND +@end example The following functions may be used within the body: - !BLANKS(count) - !CONCAT(arg@dots{}) - !EVAL(arg) - !HEAD(arg) - !INDEX(haystack, needle) - !LENGTH(arg) - !NULL - !QUOTE(arg) - !SUBSTR(arg, start[, count]) - !TAIL(arg) - !UNQUOTE(arg) - !UPCASE(arg) +@display +@t{!BLANKS(}@i{count}@t{)} +@t{!CONCAT(}@i{arg}@dots{}@t{)} +@t{!EVAL(}@i{arg}@t{)} +@t{!HEAD(}@i{arg}@t{)} +@t{!INDEX(}@i{haystack}@t{,} @i{needle}@t{)} +@t{!LENGTH(}@i{arg}@t{)} +@t{!NULL} +@t{!QUOTE(}@i{arg}@t{)} +@t{!SUBSTR(}@i{arg}@t{,} @i{start}[@t{,} @i{count}]@t{)} +@t{!TAIL(}@i{arg}@t{)} +@t{!UNQUOTE(}@i{arg}@t{)} +@t{!UPCASE(}@i{arg}@t{)} +@end display The body may also include the following constructs: +@display +@t{!IF (}@i{condition}@t{) !THEN} @i{true-expansion} @t{!ENDIF} +@t{!IF (}@i{condition}@t{) !THEN} @i{true-expansion} @t{!ELSE} @i{false-expansion} @t{!ENDIF} - !IF (condition) !THEN true-expansion !ENDIF - !IF (condition) !THEN true-expansion !ELSE false-expansion !ENDIF +@t{!DO} @i{!var} @t{=} @i{start} @t{!TO} @i{end} [@t{!BY} @i{step}] + @i{body} +@t{!DOEND} +@t{!DO} @i{!var} @t{!IN} @t{(}@i{expression}@t{)} + @i{body} +@t{!DOEND} - !DO !var = start !TO end [!BY step] - body - !DOEND - !DO !var !IN (expression) - body - !DOEND +@t{!LET} @i{!var} @t{=} @i{expression} @end display -The DEFINE command defines a macro that can later be called any number -of times within a syntax file. Each time it is called, the macro's -body is @dfn{expanded}, that is, substituted, as if the body had been -written instead of the macro call. A macro may accept arguments, -whose values are specified at the point of invocation and expanded in -the body where they are referenced. Macro bodies may also use various -directives and functions, which are also expanded when the macro is -called. +@subsection Introduction -Many identifiers associated with macros begin with @samp{!}, a -character not normally allowed in identifiers. These identifiers are -reserved only for use with macros, which helps keep them from being -confused with other kinds of identifiers. +The DEFINE command creates a @dfn{macro}, which is a name for a +fragment of PSPP syntax called the macro's @dfn{body}. Following the +DEFINE command, syntax may @dfn{call} the macro by name any number of +times. Each call substitutes, or @dfn{expands}, the macro's body in +place of the call, as if the body had been written in its place. -@node Macro Basics -@subsection Macro Basics - -The simplest macros have no arguments. The following defines a macro -named @code{!vars} that expands to the variable names @code{v1 v2 v3}, -along with a few example uses. The macro's name begins with @samp{!}, -which is optional for macro names. The @code{()} following the macro -name are required: +The following syntax defines a macro named @code{!vars} that expands +to the variable names @code{v1 v2 v3}. The macro's name begins with +@samp{!}, which is optional for macro names. The @code{()} following +the macro name are required: @example DEFINE !vars() v1 v2 v3 !ENDDEFINE. +@end example +Here are two ways that @code{!vars} might be called given the +preceding definition: + +@example DESCRIPTIVES !vars. FREQUENCIES /VARIABLES=!vars. @end example -Macros can also expand to entire commands. For example, the following -example performs the same analyses as the last one: +With macro expansion, the above calls are equivalent to the following: @example -DEFINE !commands() DESCRIPTIVES v1 v2 v3. FREQUENCIES /VARIABLES=v1 v2 v3. -!ENDDEFINE. - -!commands @end example -The body of a macro can call another macro. For example, we could -combine the two preceding examples, with @code{!commands} calling -@code{!vars} to obtain the variables to analyze. The following shows -one way that could work: +The @code{!vars} macro expands to a fixed body. Macros may have more +sophisticated contents: + +@itemize @bullet +@item +Macro @dfn{arguments} that are substituted into the body whenever they +are named. The values of a macro's arguments are specified each time +it is called. @xref{Macro Arguments}. + +@item +Macro @dfn{functions}, expanded when the macro is called. @xref{Macro +Functions}. + +@item +@code{!IF} constructs, for conditional expansion. @xref{Macro +Conditional Expansion}. + +@item +Two forms of @code{!DO} construct, for looping over a numerical range +or a collection of tokens. @xref{Macro Loops}. + +@item +@code{!LET} constructs, for assigning to macro variables. @xref{Macro +Variable Assignment}. +@end itemize + +Many identifiers associated with macros begin with @samp{!}, a +character not normally allowed in identifiers. These identifiers are +reserved only for use with macros, which helps keep them from being +confused with other kinds of identifiers. + +The following sections provide more details on macro syntax and +semantics. + +@node Macro Bodies +@subsection Macro Bodies + +As previously shown, a macro body may contain a fragment of a PSPP +command (such as a variable name). A macro body may also contain full +PSPP commands. In the latter case, the macro body should also contain +the command terminators. + +Most PSPP commands may occur within a macro. The @code{DEFINE} +command itself is one exception, because the inner @code{!ENDDEFINE} +ends the outer macro definition. For compatibility, @code{BEGIN +DATA}@dots{}@code{END DATA.} should not be used within a macro. + +The body of a macro may call another macro. The following shows one +way that could work: @example DEFINE !commands() @@ -155,10 +203,39 @@ The following section shows how to do that. @node Macro Arguments @subsection Macro Arguments -Macros may take any number of arguments, which are specified within -the parentheses in the DEFINE command. Arguments come in two -varieties based on how their values are specified when the macro is -called: +This section explains how to use macro arguments. As an initial +example, the following syntax defines a macro named @code{!analyze} +that takes all the syntax up to the first command terminator as an +argument: + +@example +DEFINE !analyze(!POSITIONAL !CMDEND) +DESCRIPTIVES !1. +FREQUENCIES /VARIABLES=!1. +!ENDDEFINE. +@end example + +@noindent When @code{!analyze} is called, it expands to a pair of analysis +commands with each @code{!1} in the body replaced by the argument. +That is, these calls: + +@example +!analyze v1 v2 v3. +!analyze v4 v5. +@end example + +@noindent act like the following: + +@example +DESCRIPTIVES v1 v2 v3. +FREQUENCIES /VARIABLES=v1 v2 v3. +DESCRIPTIVES v4 v5. +FREQUENCIES /VARIABLES=v4 v5. +@end example + +Macros may take any number of arguments, described within the +parentheses in the DEFINE command. Arguments come in two varieties +based on how their values are specified when the macro is called: @itemize @bullet @item @@ -166,10 +243,13 @@ 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. + 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. In addition, @code{!*} expands to all of the positional -argument values, separated by a space. +arguments' values, separated by spaces. The following example uses a positional argument: @@ -185,10 +265,10 @@ FREQUENCIES /VARIABLES=!1. @item A @dfn{keyword} argument has a name. In the macro call, its value is -specified with the syntax @code{@var{name}=@var{value}}. Because of -the names, keyword argument values may take any order in a macro call. -If one is omitted, then a default value is used: either the value -specified in @code{!DEFAULT(@var{value})}, or an empty value +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. In declaration and calls, a keyword argument's name may not begin with @@ -205,7 +285,7 @@ FREQUENCIES /VARIABLES=!vars. !ENDDEFINE. !analyze_kw vars=v1 v2 v3. /* Analyze specified variables. -!analyze_kw. /* Analyze all variables. +!analyze_kw. /* Analyze all variables. @end example @end itemize @@ -216,7 +296,7 @@ values also come first in macro calls. Each argument declaration specifies the form of its value: @table @code -@item !TOKENS(@var{count}) +@item !TOKENS(@i{count}) Exactly @var{count} tokens, e.g.@: @code{!TOKENS(1)} for a single token. Each identifier, number, quoted string, operator, or punctuator is a token. @xref{Tokens}, for a complete definition. @@ -291,7 +371,7 @@ FREQUENCIES /VARIABLES=!vars. 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. @xref{Controlling Macro Expansion}, for details. +expansion. @xref{Controlling Macro Expansion}. @node Controlling Macro Expansion @subsection Controlling Macro Expansion @@ -306,20 +386,20 @@ If a macro body contains @code{!OFFEXPAND} or @code{!ONEXPAND} directives, then @code{!OFFEXPAND} disables expansion of macro calls until the following @code{!ONEXPAND}. -A macro argument's value may contain a macro call. By default, these -macro calls are expanded. If the argument was declared with the -@code{!NOEXPAND} keyword, they are not expanded. +A macro argument's value may contain a macro call. These macro calls +are expanded, unless the argument was declared with the +@code{!NOEXPAND} keyword. The argument to a macro function is a special context that does not expand macro calls. For example, if @code{!vars} is the name of a macro, then @code{!LENGTH(!vars)} expands to 5, as does @code{!LENGTH(!1)} if positional argument 1 has value @code{!vars}. -In these cases, use the @code{!EVAL} macro function to expand macros, +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. Uses of macro functions and macro -arguments within a macro body are always expanded. +These rules apply to macro calls, not to uses of macro functions and +macro arguments within a macro body, which are always expanded. @node Macro Functions @subsection Macro Functions @@ -330,9 +410,15 @@ characters. The arguments to macro functions have a restricted form. They may only be a single token (such as an identifier or a string), a macro -argument, or a call to a macro function. Thus, @code{x}, @code{5.0}, -@code{x}, @code{!1}, @code{"5 + 6"}, and @code{!CONCAT(x,y)} are valid -macro arguments, but @code{x y} and @code{5 + 6} are not. +argument, or a call to a macro function. Thus, the following are +valid macro arguments: +@example +x 5.0 x !1 "5 + 6" !CONCAT(x,y) +@end example +@noindent and the following are not: +@example +x y 5+6 +@end example Macro functions expand to sequences of characters. When these character strings are processed further as character strings, e.g.@: @@ -409,7 +495,8 @@ than a single one. For example: @deffn {Macro Function} !EVAL (arg) Expands macro calls in @var{arg}. This is especially useful if @var{arg} is the name of a macro or a macro argument that expands to -one, because arguments to macro functions are not expanded by default. +one, because arguments to macro functions are not expanded by default +(@pxref{Controlling Macro Expansion}). The following examples assume that @code{!vars} is a macro that expands to @code{a b c}: @@ -621,6 +708,11 @@ body} multiple times, each time setting a named @dfn{loop variable} to a different value. The loop body typically expands the loop variable at least once. +The MITERATE setting (@pxref{SET MITERATE}) limits the number of +iterations in a loop. This is a safety measure to ensure that macro +expansion terminates. PSPP issues a warning when the MITERATE limit +is exceeded. + @subsubheading Loops Over Ranges @example @@ -694,7 +786,8 @@ calls, that is, the nesting level of macro expansion. The default is 50. This is mainly useful to avoid infinite expansion in the case of a macro that calls itself. -MITERATE +MITERATE (@pxref{SET MITERATE}) limits the number of iterations in a +@code{!DO} construct. The default is 1000. PRESERVE...RESTORE @@ -703,13 +796,12 @@ SET MEXPAND, etc. doesn't work inside macro bodies. @node Macro Notes @subsection Extra Notes -@code{!*} expands to all the positional arguments. - Macros in comments. Macros in titles. Define ``unquote.'' + @node DO IF @section DO IF @vindex DO IF diff --git a/src/language/control/define.c b/src/language/control/define.c index c21c66a73c..e91505c726 100644 --- a/src/language/control/define.c +++ b/src/language/control/define.c @@ -113,9 +113,23 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED) } else { + if (lex_token (lexer) == T_MACRO_ID) + { + lex_error (lexer, _("Keyword macro parameter must be named in " + "definition without \"!\" prefix.")); + goto error; + } if (!lex_force_id (lexer)) goto error; + if (is_macro_keyword (lex_tokss (lexer))) + { + lex_error (lexer, _("Cannot use macro keyword \"%s\" " + "as an argument name."), + lex_tokcstr (lexer)); + goto error; + } + p->positional = false; p->name = xasprintf ("!%s", lex_tokcstr (lexer)); lex_get (lexer); diff --git a/src/language/lexer/macro.c b/src/language/lexer/macro.c index 6d7decb40f..2accd94df9 100644 --- a/src/language/lexer/macro.c +++ b/src/language/lexer/macro.c @@ -32,6 +32,7 @@ #include "libpspp/str.h" #include "libpspp/string-array.h" #include "libpspp/string-map.h" +#include "libpspp/stringi-set.h" #include "gl/c-ctype.h" #include "gl/ftoastr.h" @@ -59,6 +60,41 @@ macro_token_to_representation (struct macro_token *mt, struct string *s) ds_put_substring (s, mt->representation); } +bool +is_macro_keyword (struct substring s) +{ + static struct stringi_set keywords = STRINGI_SET_INITIALIZER (keywords); + if (stringi_set_is_empty (&keywords)) + { + static const char *kws[] = { + "BREAK", + "CHAREND", + "CMDEND", + "DEFAULT", + "DO", + "DOEND", + "ELSE", + "ENCLOSE", + "ENDDEFINE", + "IF", + "IFEND", + "IN", + "LET", + "NOEXPAND", + "OFFEXPAND", + "ONEXPAND", + "POSITIONAL", + "THEN", + "TOKENS", + }; + for (size_t i = 0; i < sizeof kws / sizeof *kws; i++) + stringi_set_insert (&keywords, kws[i]); + } + + ss_ltrim (&s, ss_cstr ("!")); + return stringi_set_contains_len (&keywords, s.string, s.length); +} + void macro_tokens_copy (struct macro_tokens *dst, const struct macro_tokens *src) { @@ -543,8 +579,7 @@ me_enclose (struct macro_expander *me, const struct macro_token *mt) static const struct macro_param * macro_find_parameter_by_name (const struct macro *m, struct substring name) { - if (ss_first (name) == '!') - ss_advance (&name, 1); + ss_ltrim (&name, ss_cstr ("!")); for (size_t i = 0; i < m->n_params; i++) { @@ -1478,6 +1513,12 @@ macro_parse_let (const struct macro_token *tokens, size_t n_tokens, return 0; } const struct substring var_name = p->token.string; + if (is_macro_keyword (var_name) + || macro_find_parameter_by_name (me->macro, var_name)) + { + printf ("cannot use argument name or macro keyword as !LET variable\n"); + return 0; + } p++; if (p >= end || p->token.type != T_EQUALS) @@ -1538,8 +1579,15 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens, return 0; } const struct substring var_name = p->token.string; + if (is_macro_keyword (var_name) + || macro_find_parameter_by_name (me->macro, var_name)) + { + printf ("cannot use argument name or macro keyword as !DO variable\n"); + return 0; + } p++; + int miterate = settings_get_miterate (); if (p < end && p->token.type == T_MACRO_ID && ss_equals_case (p->token.string, ss_cstr ("!IN"))) { @@ -1568,6 +1616,11 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens, }; for (size_t i = 0; i < items.n; i++) { + if (i >= miterate) + { + printf ("exceeded maximum number of iterations %d\n", miterate); + break; + } string_map_replace_nocopy (vars, ss_xstrdup (var_name), ss_xstrdup (items.mts[i].representation)); @@ -1625,21 +1678,31 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens, }; if ((by > 0 && first <= last) || (by < 0 && first >= last)) - for (double index = first; - by > 0 ? (index <= last) : (index >= last); - index += by) - { - char index_s[DBL_BUFSIZE_BOUND]; - c_dtoastr (index_s, sizeof index_s, 0, 0, index); - string_map_replace_nocopy (vars, ss_xstrdup (var_name), - xstrdup (index_s)); - - bool break_ = false; - macro_expand (&inner, nesting_countdown, macros, - me, vars, expand, &break_, exp); - if (break_) - break; - } + { + int i = 0; + for (double index = first; + by > 0 ? (index <= last) : (index >= last); + index += by) + { + if (i++ > miterate) + { + printf ("exceeded maximum number of iterations %d\n", + miterate); + break; + } + + char index_s[DBL_BUFSIZE_BOUND]; + c_dtoastr (index_s, sizeof index_s, 0, 0, index); + string_map_replace_nocopy (vars, ss_xstrdup (var_name), + xstrdup (index_s)); + + bool break_ = false; + macro_expand (&inner, nesting_countdown, macros, + me, vars, expand, &break_, exp); + if (break_) + break; + } + } return do_end - tokens + 1; } diff --git a/src/language/lexer/macro.h b/src/language/lexer/macro.h index 0b6fd0dac3..ac5cb65579 100644 --- a/src/language/lexer/macro.h +++ b/src/language/lexer/macro.h @@ -38,6 +38,8 @@ void macro_token_uninit (struct macro_token *); void macro_token_to_representation (struct macro_token *, struct string *); +bool is_macro_keyword (struct substring); + struct macro_tokens { struct macro_token *mts; diff --git a/tests/language/control/define.at b/tests/language/control/define.at index fa2670f9e6..fa87f9a1de 100644 --- a/tests/language/control/define.at +++ b/tests/language/control/define.at @@ -275,6 +275,24 @@ note: unexpanded token "c" ]) AT_CLEANUP +AT_SETUP([keyword macro argument name with ! prefix]) +AT_DATA([define.sps], [dnl +DEFINE !macro(!x=!TOKENS(1). +]) +AT_CHECK([pspp -O format=csv define.sps], [1], [dnl +"define.sps:1.15-1.16: error: DEFINE: Syntax error at `!x': Keyword macro parameter must be named in definition without ""!"" prefix." +]) +AT_CLEANUP + +AT_SETUP([reserved macro keyword argument name]) +AT_DATA([define.sps], [dnl +DEFINE !macro(if=!TOKENS(1). +]) +AT_CHECK([pspp -O format=csv define.sps], [1], [dnl +"define.sps:1.15-1.16: error: DEFINE: Syntax error at `if': Cannot use macro keyword ""if"" as an argument name." +]) +AT_CLEANUP + PSPP_CHECK_MACRO_EXPANSION([one !TOKENS(1) keyword argument], [DEFINE !k(arg1 = !TOKENS(1)) k(!arg1) !ENDDEFINE.], [!k arg1=x. @@ -819,6 +837,96 @@ AT_CHECK([pspp --testing-mode define.sps], [0], [dnl ]) AT_CLEANUP +AT_SETUP([macro !DO invalid variable names]) +AT_KEYWORDS([index do]) +AT_DATA([define.sps], [dnl +DEFINE !for(x=!TOKENS(1) / y=!TOKENS(1)) +!DO !x = !x !TO !y !var !DOEND. +!ENDDEFINE. + +DEFINE !for2(x=!TOKENS(1) / y=!TOKENS(1)) +!DO !noexpand = !x !TO !y !var !DOEND. +!ENDDEFINE. + +DEBUG EXPAND. +!for x=1 y=5. +!for2 x=1 y=5. +]) +AT_CHECK([pspp --testing-mode define.sps], [0], [dnl +cannot use argument name or macro keyword as !DO variable +cannot use argument name or macro keyword as !DO variable +!DO 1 = 1 !TO 5 !var !DOEND. + +!DO !noexpand = 1 !TO 5 !var !DOEND. +]) +AT_CLEANUP + +AT_SETUP([macro indexed !DO reaches MITERATE]) +AT_KEYWORDS([index do]) +AT_DATA([define.sps], [dnl +DEFINE !title(!POS !TOKENS(1)) !1. !ENDDEFINE. + +DEFINE !for(!POS !TOKENS(1) / !POS !TOKENS(1)) +!DO !var = !1 !TO !2 !var !DOEND. +!ENDDEFINE. + +DEFINE !forby(!POS !TOKENS(1) / !POS !TOKENS(1) / !POS !TOKENS(1)) +!DO !var = !1 !TO !2 !BY !3 !var !DOEND. +!ENDDEFINE. + +SET MITERATE=3. +DEBUG EXPAND. +!title "increasing". +!for 1 5. +!forby 1 5 1. +!forby 1 5 2. +!forby 1 5 2.5. +!forby 1 5 -1. + +!title "decreasing". +!for 5 1. +!forby 5 1 1. +!forby 5 1 -1. +!forby 5 1 -2. +!forby 5 1 -3. + +!title "non-integer". +!for 1.5 3.5. +]) +AT_CHECK([pspp --testing-mode define.sps], [0], [dnl +exceeded maximum number of iterations 3 +exceeded maximum number of iterations 3 +exceeded maximum number of iterations 3 +"increasing". + +1 2 3 4. + +1 2 3 4. + +1 3 5. + +1 3.5. + +. + +"decreasing". + +. + +. + +5 4 3 2. + +5 3 1. + +5 2. + +"non-integer". + +1.5 2.5 3.5. +]) +AT_CLEANUP + AT_SETUP([!BREAK with macro indexed !DO]) AT_KEYWORDS([index do break]) AT_DATA([define.sps], [dnl @@ -867,6 +975,30 @@ AT_CHECK([pspp --testing-mode define.sps], [0], [dnl ]) AT_CLEANUP +AT_SETUP([macro list !DO reaches MITERATE]) +AT_KEYWORDS([index do]) +AT_DATA([define.sps], [dnl +DEFINE !for(!POS !CMDEND) +(!DO !i !IN (!1) (!i) !DOEND). +!ENDDEFINE. + +SET MITERATE=2. +DEBUG EXPAND. +!for a b c. +!for 'foo bar baz quux'. +!for. +]) +AT_CHECK([pspp --testing-mode define.sps], [0], [dnl +exceeded maximum number of iterations 2 +exceeded maximum number of iterations 2 +( (a) (b) ). + +( (foo) (bar) ). + +( ). +]) +AT_CLEANUP + AT_SETUP([!BREAK with macro list !DO]) AT_KEYWORDS([index break do]) AT_DATA([define.sps], [dnl @@ -897,3 +1029,87 @@ AT_CHECK([pspp --testing-mode define.sps], [0], [dnl ( ). ]) AT_CLEANUP + +AT_SETUP([macro !LET]) +AT_DATA([define.sps], [dnl +DEFINE !macro(!pos !enclose('(',')')) +!LET !x=!1 +!LET !y=!QUOTE(!1) +!LET !z=(!y="abc") +!y !z +!ENDDEFINE. + +DEBUG EXPAND. +!macro(1+2). +!macro(abc). +]) +AT_CHECK([pspp --testing-mode define.sps -O format=csv], [0], [dnl +1 + 2 0 + +abc 1 +]) +AT_CLEANUP + +AT_SETUP([macro !LET invalid variable names]) +AT_DATA([define.sps], [dnl +DEFINE !macro(x=!tokens(1)) +!LET !x=!x +!ENDDEFINE. + +DEFINE !macro2() +!LET !do=x +!ENDDEFINE. + +DEBUG EXPAND. +!macro 1. +!macro2. +]) +AT_CHECK([pspp --testing-mode define.sps -O format=csv], [0], [dnl +cannot use argument name or macro keyword as !LET variable +cannot use argument name or macro keyword as !LET variable +expected macro variable name following !DO +!LET = + +!LET !do = x +]) +AT_CLEANUP + +AT_SETUP([BEGIN DATA inside a macro]) +AT_DATA([define.sps], [dnl +DEFINE !macro() +DATA LIST NOTABLE /x 1. +BEGIN DATA +1 +2 +3 +END DATA. +LIST. +!ENDDEFINE. + +!macro +]) +AT_CHECK([pspp define.sps -O format=csv], [0], [dnl +Table: Data List +x +1 +2 +3 +]) +AT_CLEANUP + +AT_SETUP([TITLE and SUBTITLE with macros]) +AT_KEYWORDS([macro]) +for command in TITLE SUBTITLE; do + cat >title.sps <expout <