From: Ben Pfaff Date: Wed, 7 May 2025 20:56:17 +0000 (-0700) Subject: work on manual X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8e5be9c311f2f5f4b702c9b5f33b36a6dd0bed86;p=pspp work on manual --- diff --git a/rust/doc/src/SUMMARY.md b/rust/doc/src/SUMMARY.md index 8e9467813b..5d589f80c1 100644 --- a/rust/doc/src/SUMMARY.md +++ b/rust/doc/src/SUMMARY.md @@ -113,6 +113,12 @@ - [SPLIT FILE](commands/selection/split-file.md) - [TEMPORARY](commands/selection/temporary.md) - [WEIGHT](commands/selection/weight.md) +- [Conditionals and Loops](commands/control/index.md) + - [BREAK](commands/control/break.md) + - [DEFINE](commands/control/define.md) + - [DO IF](commands/control/do-if.md) + - [DO REPEAT](commands/control/do-repeat.md) + - [LOOP](commands/control/loop.md) # Developer Documentation diff --git a/rust/doc/src/commands/control/break.md b/rust/doc/src/commands/control/break.md new file mode 100644 index 0000000000..866a703681 --- /dev/null +++ b/rust/doc/src/commands/control/break.md @@ -0,0 +1,11 @@ +# BREAK + +``` +BREAK. +``` + +`BREAK` terminates execution of the innermost currently executing +`LOOP` construct. + +`BREAK` is allowed only inside [`LOOP`...`END LOOP`](loop.md). + diff --git a/rust/doc/src/commands/control/define.md b/rust/doc/src/commands/control/define.md new file mode 100644 index 0000000000..eddfa208ee --- /dev/null +++ b/rust/doc/src/commands/control/define.md @@ -0,0 +1,766 @@ +# DEFINE + +## Overview + +``` +DEFINE macro_name([argument[/argument]...]) +...body... +!ENDDEFINE. +``` + +Each argument takes the following form: +``` +{!arg_name= | !POSITIONAL} +[!DEFAULT(default)] +[!NOEXPAND] +{!TOKENS(count) | !CHAREND('token') | !ENCLOSE('start' | 'end') | !CMDEND} +``` + +The following directives may be used within body: +``` +!OFFEXPAND +!ONEXPAND +``` + +The following functions may be used within the body: +``` +!BLANKS(count) +!CONCAT(arg...) +!EVAL(arg) +!HEAD(arg) +!INDEX(haystack, needle) +!LENGTH(arg) +!NULL +!QUOTE(arg) +!SUBSTR(arg, start[, count]) +!TAIL(arg) +!UNQUOTE(arg) +!UPCASE(arg) +``` + +The body may also include the following constructs: +``` +!IF (condition) !THEN true-expansion !ENDIF +!IF (condition) !THEN true-expansion !ELSE false-expansion !ENDIF + +!DO !var = start !TO end [!BY step] + body +!DOEND +!DO !var !IN (expression) + body +!DOEND + +!LET !var = expression +``` + +## Introduction + +The DEFINE command creates a "macro", which is a name for a fragment of +PSPP syntax called the macro's "body". Following the DEFINE command, +syntax may "call" the macro by name any number of times. Each call +substitutes, or "expands", the macro's body in place of the call, as if +the body had been written in its place. + +The following syntax defines a macro named `!vars` that expands to +the variable names `v1 v2 v3`. The macro's name begins with `!`, which +is optional for macro names. The `()` following the macro name are +required: + +``` +DEFINE !vars() +v1 v2 v3 +!ENDDEFINE. +``` + +Here are two ways that `!vars` might be called given the preceding +definition: + +``` +DESCRIPTIVES !vars. +FREQUENCIES /VARIABLES=!vars. +``` + +With macro expansion, the above calls are equivalent to the +following: + +``` +DESCRIPTIVES v1 v2 v3. +FREQUENCIES /VARIABLES=v1 v2 v3. +``` + +The `!vars` macro expands to a fixed body. Macros may have more +sophisticated contents: + +- Macro "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. *Note Macro Arguments::. + +- Macro "[functions](#macro-functions)", expanded when the macro is + called. + +- [`!IF` constructs](#macro-conditional-expansion), for conditional expansion. + +- Two forms of [`!DO` construct](#macro-loops), for looping over a + numerical range or a collection of tokens. + +- [`!LET` constructs](#macro-variable-assignment), for assigning to + macro variables. + +Many identifiers associated with macros begin with `!`, 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. + +## 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 `DEFINE` command +itself is one exception, because the inner `!ENDDEFINE` ends the outer +macro definition. For compatibility, `BEGIN DATA`...`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: + +``` +DEFINE !commands() +DESCRIPTIVES !vars. +FREQUENCIES /VARIABLES=!vars. +!ENDDEFINE. + +* Initially define the 'vars' macro to analyze v1...v3. +DEFINE !vars() v1 v2 v3 !ENDDEFINE. +!commands + +* Redefine 'vars' macro to analyze different variables. +DEFINE !vars() v4 v5 !ENDDEFINE. +!commands +``` + +The `!commands` macro would be easier to use if it took the variables +to analyze as an argument rather than through another macro. The +following section shows how to do that. + +## Macro Arguments + +This section explains how to use macro arguments. As an initial +example, the following syntax defines a macro named `!analyze` that +takes all the syntax up to the first command terminator as an argument: + +``` +DEFINE !analyze(!POSITIONAL !CMDEND) +DESCRIPTIVES !1. +FREQUENCIES /VARIABLES=!1. +!ENDDEFINE. +``` + +When `!analyze` is called, it expands to a pair of analysis commands +with each `!1` in the body replaced by the argument. That is, these +calls: + +``` +!analyze v1 v2 v3. +!analyze v4 v5. +``` + +act like the following: + +``` +DESCRIPTIVES v1 v2 v3. +FREQUENCIES /VARIABLES=v1 v2 v3. +DESCRIPTIVES v4 v5. +FREQUENCIES /VARIABLES=v4 v5. +``` + +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: + +- A "positional" argument has a required value that follows the + macro's name. Use the `!POSITIONAL` keyword to declare a + positional argument. + + 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: + `!1` is the first positional argument, `!2` the second, and so on. + In addition, `!*` expands to all of the positional arguments' + values, separated by spaces. + + The following example uses a positional argument: + + ``` + DEFINE !analyze(!POSITIONAL !CMDEND) + DESCRIPTIVES !1. + FREQUENCIES /VARIABLES=!1. + !ENDDEFINE. + + !analyze v1 v2 v3. + !analyze v4 v5. + ``` + +- A "keyword" argument has a name. In the macro call, its value is + specified with the syntax `name=value`. The names allow keyword + argument values to take any order in the call. + + In declaration and calls, a keyword argument's name may not begin + with `!`, but references to it in the macro body do start with a + leading `!`. + + The following example uses a keyword argument that defaults to ALL + if the argument is not assigned a value: + + ``` + DEFINE !analyze_kw(vars=!DEFAULT(ALL) !CMDEND) + DESCRIPTIVES !vars. + FREQUENCIES /VARIABLES=!vars. + !ENDDEFINE. + + !analyze_kw vars=v1 v2 v3. /* Analyze specified variables. + !analyze_kw. /* Analyze all variables. + ``` + +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. 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 `!DEFAULT(value)`, or an empty value otherwise. + +Each argument declaration specifies the form of its value: + +* `!TOKENS(count)` + Exactly `count` tokens, e.g. `!TOKENS(1)` for a single token. Each + identifier, number, quoted string, operator, or punctuator is a + token (see [Tokens](../../language/basics/tokens.md) for details). + + The following variant of `!analyze_kw` accepts only a single + variable name (or `ALL`) as its argument: + + ``` + DEFINE !analyze_one_var(!POSITIONAL !TOKENS(1)) + DESCRIPTIVES !1. + FREQUENCIES /VARIABLES=!1. + !ENDDEFINE. + + !analyze_one_var v1. + ``` + +* `!CHAREND('TOKEN')` + Any number of tokens up to `TOKEN`, which should be an operator or + punctuator token such as `/` or `+`. The `TOKEN` does not become + part of the value. + + With the following variant of `!analyze_kw`, the variables must be + following by `/`: + + ``` + DEFINE !analyze_parens(vars=!CHARNED('/')) + DESCRIPTIVES !vars. + FREQUENCIES /VARIABLES=!vars. + !ENDDEFINE. + + !analyze_parens vars=v1 v2 v3/. + ``` + +* `!ENCLOSE('START','END')` + Any number of tokens enclosed between `START` and `END`, which + should each be operator or punctuator tokens. For example, use + `!ENCLOSE('(',')')` for a value enclosed within parentheses. (Such + a value could never have right parentheses inside it, even paired + with left parentheses.) The start and end tokens are not part of + the value. + + With the following variant of `!analyze_kw`, the variables must be + specified within parentheses: + + ``` + DEFINE !analyze_parens(vars=!ENCLOSE('(',')')) + DESCRIPTIVES !vars. + FREQUENCIES /VARIABLES=!vars. + !ENDDEFINE. + + !analyze_parens vars=(v1 v2 v3). + ``` + +* `!CMDEND` + 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 `!analyze_kw` takes all the variable names + up to the end of the command as its argument: + + ``` + DEFINE !analyze_kw(vars=!CMDEND) + DESCRIPTIVES !vars. + FREQUENCIES /VARIABLES=!vars. + !ENDDEFINE. + + !analyze_kw vars=v1 v2 v3. + ``` + +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 +[`!NOEXPAND` keyword](#controlling-macro-expansion) in an argument +declaration suppresses this expansion. + +## Controlling Macro Expansion + +Multiple factors control whether macro calls are expanded in different +situations. At the highest level, `SET MEXPAND` controls whether +macro calls are expanded. By default, it is enabled. *Note SET +MEXPAND::, for details. + +A macro body may contain macro calls. By default, these are expanded. +If a macro body contains `!OFFEXPAND` or `!ONEXPAND` directives, then +`!OFFEXPAND` disables expansion of macro calls until the following +`!ONEXPAND`. + +A macro argument's value may contain a macro call. These macro calls +are expanded, unless the argument was declared with the `!NOEXPAND` +keyword. + +The argument to a macro function is a special context that does not +expand macro calls. For example, if `!vars` is the name of a macro, +then `!LENGTH(!vars)` expands to 5, as does `!LENGTH(!1)` if +positional argument 1 has value `!vars`. To expand macros in these +cases, use the [`!EVAL` macro function](#eval), +e.g. `!LENGTH(!EVAL(!vars))` or `!LENGTH(!EVAL(!1))`. + +These rules apply to macro calls, not to uses within a macro body of +macro functions, macro arguments, and macro variables created by `!DO` +or `!LET`, which are always expanded. + +`SET MEXPAND` may appear within the body of a macro, but it will not +affect expansion of the macro that it appears in. Use `!OFFEXPAND` +and `!ONEXPAND` instead. + +## Macro Functions + +Macro bodies may manipulate syntax using macro functions. Macro +functions accept tokens as arguments and expand to sequences of +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, the following are valid +macro arguments: + +- `x` +- `5.0` +- `x` +- `!1` +- `"5 + 6"` +- `!CONCAT(x,y)` + +and the following are not (because they are each multiple tokens): + +- `x y` +- `5+6` + +Macro functions expand to sequences of characters. When these +character strings are processed further as character strings, +e.g. with `!LENGTH`, any character string is valid. When they are +interpreted as PSPP syntax, e.g. when the expansion becomes part of a +command, they need to be valid for that purpose. For example, +`!UNQUOTE("It's")` will yield an error if the expansion `It's` becomes +part of a PSPP command, because it contains unbalanced single quotes, +but `!LENGTH(!UNQUOTE("It's"))` expands to 4. + +The following macro functions are available. + +* `!BLANKS(count)` + Expands to COUNT unquoted spaces, where COUNT is a nonnegative + integer. Outside quotes, any positive number of spaces are + equivalent; for a quoted string of spaces, use + `!QUOTE(!BLANKS(COUNT))`. + + In the examples below, `_` stands in for a space to make the + results visible. + + ``` + !BLANKS(0) ↦ empty + !BLANKS(1) ↦ _ + !BLANKS(2) ↦ __ + !QUOTE(!BLANKS(5)) ↦ '_____' + ``` + + |Call|Expansion| + |:-----|:--------| + |`!BLANKS(0)`|(empty)| + |`!BLANKS(1)`|`_`| + |`!BLANKS(2)`|`__`| + |`!QUOTE(!BLANKS(5))|`'_____'`| + +* `!CONCAT(arg...)` + Expands to the concatenation of all of the arguments. Before + concatenation, each quoted string argument is unquoted, as if + `!UNQUOTE` were applied. This allows for "token pasting", + combining two (or more) tokens into a single one: + + |Call|Expansion| + |:-----|:--------| + |`!CONCAT(x, y)`|`xy`| + |`!CONCAT('x', 'y')`|`xy`| + |`!CONCAT(12, 34)`|`1234`| + |`!CONCAT(!NULL, 123)`|`123`| + + `!CONCAT` is often used for constructing a series of similar + variable names from a prefix followed by a number and perhaps a + suffix. For example: + + |Call|Expansion| + |:-----|:--------| + |`!CONCAT(x, 0)`|`x0`| + |`!CONCAT(x, 0, y)`|`x0y`| + + An identifier token must begin with a letter (or `#` or `@`), which + means that attempting to use a number as the first part of an + identifier will produce a pair of distinct tokens rather than a + single one. For example: + + |Call|Expansion| + |:-----|:--------| + |`!CONCAT(0, x)`|`0 x`| + |`!CONCAT(0, x, y)`|`0 xy`| + +* `!EVAL(arg)` + Expands macro calls in ARG. This is especially useful if 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 + (*note Controlling Macro Expansion::). + + The following examples assume that `!vars` is a macro that expands + to `a b c`: + + |Call|Expansion| + |:-----|:--------| + |`!vars`|`a b c`| + |`!QUOTE(!vars)`|`'!vars'`| + |`!EVAL(!vars)`|`a b c`| + |`!QUOTE(!EVAL(!vars))`|`'a b c'`| + + These examples additionally assume that argument `!1` has value + `!vars`: + + |Call|Expansion| + |:-----|:--------| + |`!1`|`a b c`| + |`!QUOTE(!1)`|`'!vars'`| + |`!EVAL(!1)`|`a b c`| + |`!QUOTE(!EVAL(!1))`|`'a b c'`| + +* `!HEAD(arg)` + `!TAIL(arg)` + `!HEAD` expands to just the first token in an unquoted version of + ARG, and `!TAIL` to all the tokens after the first. + + |Call|Expansion| + |:-----|:--------| + |`!HEAD('a b c')`|`a`| + |`!HEAD('a')`|`a`| + |`!HEAD(!NULL)`|(empty)| + |`!HEAD('')`|(empty)| + |`!TAIL('a b c')`|`b c`| + |`!TAIL('a')`|(empty)| + |`!TAIL(!NULL)`|(empty)| + |`!TAIL('')`|(empty)| + +* `!INDEX(haystack, needle)` + Looks for NEEDLE in HAYSTACK. If it is present, expands to the + 1-based index of its first occurrence; if not, expands to 0. + + |Call|Expansion| + |:-----|:--------| + |`!INDEX(banana, an)`|`2`| + |`!INDEX(banana, nan)`|`3`| + |`!INDEX(banana, apple)`|`0`| + |`!INDEX("banana", nan)`|`4`| + |`!INDEX("banana", "nan")`|`0`| + |`!INDEX(!UNQUOTE("banana"), !UNQUOTE("nan"))`|`3`| + +* `!LENGTH(arg)` + Expands to a number token representing the number of characters in + ARG. + + |Call|Expansion| + |:-----|:--------| + |`!LENGTH(123)`|`3`| + |`!LENGTH(123.00)`|`6`| + |`!LENGTH( 123 )`|`3`| + |`!LENGTH("123")`|`5`| + |`!LENGTH(xyzzy)`|`5`| + |`!LENGTH("xyzzy")`|`7`| + |`!LENGTH("xy""zzy")`|`9`| + |`!LENGTH(!UNQUOTE("xyzzy"))`|`5`| + |`!LENGTH(!UNQUOTE("xy""zzy"))`|`6`| + |`!LENGTH(!1)`|`5` (if `!1` is `a b c`)| + |`!LENGTH(!1)`|`0` (if `!1` is empty)| + |`!LENGTH(!NULL)`|`0`| + +* `!NULL` + Expands to an empty character sequence. + + |Call|Expansion| + |:-----|:--------| + |`!NULL`|(empty)| + |`!QUOTE(!NULL)`|`''`| + +* `!QUOTE(arg)` + `!UNQUOTE(arg)` + The `!QUOTE` function expands to its argument surrounded by + apostrophes, doubling any apostrophes inside the argument to make + sure that it is valid PSPP syntax for a string. If the argument + was already a quoted string, `!QUOTE` expands to it unchanged. + + Given a quoted string argument, the `!UNQUOTED` function expands to + the string's contents, with the quotes removed and any doubled + quote marks reduced to singletons. If the argument was not a + quoted string, `!UNQUOTE` expands to the argument unchanged. + + |Call|Expansion| + |:-----|:--------| + |`!QUOTE(123.0)`|`'123.0'`| + |`!QUOTE( 123 )`|`'123'`| + |`!QUOTE('a b c')`|`'a b c'`| + |`!QUOTE("a b c")`|`"a b c"`| + |`!QUOTE(!1)`|`'a ''b'' c'` (if `!1` is `a 'b' c`)| + |`!UNQUOTE(123.0)`|`123.0`| + |`!UNQUOTE( 123 )`|`123`| + |`!UNQUOTE('a b c')`|`a b c`| + |`!UNQUOTE("a b c")`|`a b c`| + |`!UNQUOTE(!1)`|`a 'b' c` (if `!1` is `a 'b' c`)| + |`!QUOTE(!UNQUOTE(123.0))`|`'123.0'`| + |`!QUOTE(!UNQUOTE( 123 ))`|`'123'`| + |`!QUOTE(!UNQUOTE('a b c'))`|`'a b c'`| + |`!QUOTE(!UNQUOTE("a b c"))`|`'a b c'`| + |`!QUOTE(!UNQUOTE(!1))`|`'a ''b'' c'` (if `!1` is `a 'b' c`)| + +* `!SUBSTR(arg, start[, count])` + Expands to a substring of ARG starting from 1-based position START. + If COUNT is given, it limits the number of characters in the + expansion; if it is omitted, then the expansion extends to the end + of ARG. + + |Call|Expansion| + |:-----|:--------| + |`!SUBSTR(banana, 3)`|`nana`| + |`!SUBSTR(banana, 3, 3)`|`nan`| + |`!SUBSTR("banana", 1, 3)`|error (`"ba` is not a valid token)| + |`!SUBSTR(!UNQUOTE("banana"), 3)`|`nana`| + |`!SUBSTR("banana", 3, 3)`|`ana`| + |`!SUBSTR(banana, 3, 0)`|(empty)| + |`!SUBSTR(banana, 3, 10)`|`nana`| + |`!SUBSTR(banana, 10, 3)`|(empty)| + ``` + +* `!UPCASE(arg)` + Expands to an unquoted version of ARG with all letters converted to + uppercase. + + |Call|Expansion| + |:-----|:--------| + |`!UPCASE(freckle)`|`FRECKLE`| + |`!UPCASE('freckle')`|`FRECKLE`| + |`!UPCASE('a b c')`|`A B C`| + |`!UPCASE('A B C')`|`A B C`| + +## Macro Expressions + +Macro expressions are used in conditional expansion and loops, which are +described in the following sections. A macro expression may use the +following operators, listed in descending order of operator precedence: + +* `()` + Parentheses override the default operator precedence. + +* `!EQ !NE !GT !LT !GE !LE = ~= <> > < >= <=` + Relational operators compare their operands and yield a Boolean + result, either `0` for false or `1` for true. + + These operators always compare their operands as strings. This can + be surprising when the strings are numbers because, e.g., `1 < 1.0` + and `10 < 2` both evaluate to `1` (true). + + Comparisons are case sensitive, so that `a = A` evaluates to `0` + (false). + +* `!NOT ~` + `!AND &` + `!OR |` + Logical operators interpret their operands as Boolean values, where + quoted or unquoted `0` is false and anything else is true, and + yield a Boolean result, either `0` for false or `1` for true. + +Macro expressions do not include any arithmetic operators. + +An operand in an expression may be a single token (including a macro +argument name) or a macro function invocation. Either way, the +expression evaluator unquotes the operand, so that `1 = '1'` is true. + +## Macro Conditional Expansion + +The `!IF` construct may be used inside a macro body to allow for +conditional expansion. It takes the following forms: + +``` +!IF (EXPRESSION) !THEN TRUE-EXPANSION !IFEND +!IF (EXPRESSION) !THEN TRUE-EXPANSION !ELSE FALSE-EXPANSION !IFEND +``` + +When `EXPRESSION` evaluates to true, the macro processor expands +`TRUE-EXPANSION`; otherwise, it expands `FALSE-EXPANSION`, if it is +present. The macro processor considers quoted or unquoted `0` to be +false, and anything else to be true. + +## Macro Loops + +The body of a macro may include two forms of loops: loops over numerical +ranges and loops over tokens. Both forms expand a "loop body" multiple +times, each time setting a named "loop variable" to a different value. +The loop body typically expands the loop variable at least once. + +The `MITERATE` setting (*note 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. + +### Loops Over Ranges + +``` +!DO !VAR = START !TO END [!BY STEP] + BODY +!DOEND +``` + +A loop over a numerical range has the form shown above. `START`, +`END`, and `STEP` (if included) must be expressions with numeric +values. The macro processor accepts both integers and real numbers. +The macro processor expands `BODY` for each numeric value from `START` +to `END`, inclusive. + +The default value for `STEP` is 1. If `STEP` is positive and `FIRST > +LAST`, or if `STEP` is negative and `FIRST < LAST`, then the macro +processor doesn't expand the body at all. `STEP` may not be zero. + +### Loops Over Tokens + +``` +!DO !VAR !IN (EXPRESSION) + BODY +!DOEND +``` + +A loop over tokens takes the form shown above. The macro processor +evaluates `EXPRESSION` and expands `BODY` once per token in the +result, substituting the token for `!VAR` each time it appears. + +## Macro Variable Assignment + +The `!LET` construct evaluates an expression and assigns the result to a +macro variable. It may create a new macro variable or change the value +of one created by a previous `!LET` or `!DO`, but it may not change the +value of a macro argument. `!LET` has the following form: + +``` +!LET !VAR = EXPRESSION +``` + +If `EXPRESSION` is more than one token, it must be enclosed in +parentheses. + +## Macro Settings + +Some macro behavior is controlled through the `SET` command (*note SET::). +This section describes these settings. + +Any `SET` command that changes these settings within a macro body only +takes effect following the macro. This is because PSPP expands a +macro's entire body at once, so that `SET` inside the body only +executes afterwards. + +The `MEXPAND` setting (*note SET MEXPAND::) controls whether macros +will be expanded at all. By default, macro expansion is on. To avoid +expansion of macros called within a macro body, use [`!OFFEXPAND` and +`!ONEXPAND`](#controlling-macro-expansion). + +When `MPRINT` (*note SET MPRINT::) is turned on, PSPP outputs an +expansion of each macro called. This feature can be useful for +debugging macro definitions. For reading the expanded version, note +that macro expansion removes comments and standardizes white space. + +`MNEST` (*note SET MNEST::) limits the depth of expansion of macro +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` (*note SET MITERATE::) limits the number of iterations in a +`!DO` construct. The default is 1000. + +## Additional Notes + +### Calling Macros from Macros + +If the body of macro A includes a call to macro B, the call can use +macro arguments (including `!*`) and macro variables as part of +arguments to B. For `!TOKENS` arguments, the argument or variable name +counts as one token regardless of the number that it expands into; for +`!CHAREND` and `!ENCLOSE` arguments, the delimiters come only from the +call, not the expansions; and `!CMDEND` ends at the calling command, not +any end of command within an argument or variable. + +Macro functions are not supported as part of the arguments in a macro +call. To get the same effect, use `!LET` to define a macro variable, +then pass the macro variable to the macro. + +When macro A calls macro B, the order of their `DEFINE` commands +doesn't matter, as long as macro B has been defined when A is called. + +### Command Terminators + +Macros and command terminators require care. Macros honor the syntax +differences between [interactive and batch +syntax](../../language/basics/syntax-variants.md), which means that +the interpretation of a macro can vary depending on the syntax mode in +use. We assume here that interactive mode is in use, in which `.` at +the end of a line is the primary way to end a command. + +The `DEFINE` command needs to end with `.` following the `!ENDDEFINE`. +The macro body may contain `.` if it is intended to expand to whole +commands, but using `.` within a macro body that expands to just +syntax fragments (such as a list of variables) will cause syntax +errors. + +Macro directives such as `!IF` and `!DO` do not end with `.`. + +### Expansion Contexts + +PSPP does not expand macros within comments, whether introduced within +a line by `/*` or as a separate `COMMENT` or `*` command (*note +COMMENT::). (SPSS does expand macros in `COMMENT` and `*`.) + +Macros do not expand within quoted strings. + +Macros are expanded in the `TITLE` and `SUBTITLE` commands as long as +their arguments are not quoted strings. + +### PRESERVE and RESTORE + +Some macro bodies might use the `SET` command to change certain +settings. When this is the case, consider using the `PRESERVE` and +`RESTORE` commands to save and then restore these settings. *Note +PRESERVE and RESTORE::. + diff --git a/rust/doc/src/commands/control/do-if.md b/rust/doc/src/commands/control/do-if.md new file mode 100644 index 0000000000..b6b2022e9a --- /dev/null +++ b/rust/doc/src/commands/control/do-if.md @@ -0,0 +1,28 @@ +# DO IF + +``` +DO IF condition. + ... +[ELSE IF condition. + ... +]... +[ELSE. + ...] +END IF. +``` + +`DO IF` allows one of several sets of transformations to be executed, +depending on user-specified conditions. + +If the specified boolean expression evaluates as true, then the block +of code following `DO IF` is executed. If it evaluates as missing, +then none of the code blocks is executed. If it is false, then the +boolean expression on the first `ELSE IF`, if present, is tested in +turn, with the same rules applied. If all expressions evaluate to +false, then the `ELSE` code block is executed, if it is present. + +When `DO IF` or `ELSE IF` is specified following +[`TEMPORARY`](../../commands/selection/temporary.md), the +[`LAG`](../../language/expressions/functions/miscellaneous.md) +function may not be used. + diff --git a/rust/doc/src/commands/control/do-repeat.md b/rust/doc/src/commands/control/do-repeat.md new file mode 100644 index 0000000000..20b029f443 --- /dev/null +++ b/rust/doc/src/commands/control/do-repeat.md @@ -0,0 +1,59 @@ +# DO REPEAT + +``` +DO REPEAT dummy_name=expansion.... + ... +END REPEAT [PRINT]. + +expansion takes one of the following forms: + var_list + num_or_range... + 'string'... + ALL + +num_or_range takes one of the following forms: + number + num1 TO num2 +``` + +`DO REPEAT` repeats a block of code, textually substituting different +variables, numbers, or strings into the block with each repetition. + +Specify a dummy variable name followed by an equals sign (`=`) and +the list of replacements. Replacements can be a list of existing or new +variables, numbers, strings, or `ALL` to specify all existing variables. +When numbers are specified, runs of increasing integers may be indicated +as `NUM1 TO NUM2`, so that `1 TO 5` is short for `1 2 3 4 5`. + +Multiple dummy variables can be specified. Each variable must have +the same number of replacements. + +The code within `DO REPEAT` is repeated as many times as there are +replacements for each variable. The first time, the first value for +each dummy variable is substituted; the second time, the second value +for each dummy variable is substituted; and so on. + +Dummy variable substitutions work like macros. They take place +anywhere in a line that the dummy variable name occurs. This includes +command and subcommand names, so command and subcommand names that +appear in the code block should not be used as dummy variable +identifiers. Dummy variable substitutions do not occur inside quoted +strings, comments, unquoted strings (such as the text on the `TITLE` +or `DOCUMENT` command), or inside `BEGIN DATA`...`END DATA`. + +Substitution occurs only on whole words, so that, for example, a dummy +variable `PRINT` would not be substituted into the word `PRINTOUT`. + +New variable names used as replacements are not automatically created +as variables, but only if used in the code block in a context that +would create them, e.g. on a `NUMERIC` or `STRING` command or on the +left side of a `COMPUTE` assignment. + +Any command may appear within `DO REPEAT`, including nested `DO +REPEAT` commands. If `INCLUDE` or `INSERT` appears within `DO +REPEAT`, the substitutions do not apply to the included file. + +If `PRINT` is specified on `END REPEAT`, the commands after +substitutions are made should be printed to the listing file, prefixed +by a plus sign (`+`). This feature is not yet implemented. + diff --git a/rust/doc/src/commands/control/index.md b/rust/doc/src/commands/control/index.md new file mode 100644 index 0000000000..3b005f993f --- /dev/null +++ b/rust/doc/src/commands/control/index.md @@ -0,0 +1,2 @@ +This chapter documents PSPP commands used for conditional execution, +looping, and flow of control. diff --git a/rust/doc/src/commands/control/loop.md b/rust/doc/src/commands/control/loop.md new file mode 100644 index 0000000000..6e98418c05 --- /dev/null +++ b/rust/doc/src/commands/control/loop.md @@ -0,0 +1,65 @@ +# LOOP + +``` +LOOP [INDEX_VAR=START TO END [BY INCR]] [IF CONDITION]. + ... +END LOOP [IF CONDITION]. +``` + +`LOOP` iterates a group of commands. A number of termination options +are offered. + +Specify `INDEX_VAR` to make that variable count from one value to +another by a particular increment. `INDEX_VAR` must be a pre-existing +numeric variable. `START`, `END`, and `INCR` are numeric +[expressions](../../language/expressions/index.md). + +During the first iteration, `INDEX_VAR` is set to the value of +`START`. During each successive iteration, `INDEX_VAR` is increased +by the value of `INCR`. If `END > START`, then the loop terminates +when `INDEX_VAR > END`; otherwise it terminates when `INDEX_VAR < +END`. If `INCR` is not specified then it defaults to +1 or -1 as +appropriate. + +If `END > START` and `INCR < 0`, or if `END < START` and `INCR > 0`, +then the loop is never executed. `INDEX_VAR` is nevertheless set to +the value of start. + +Modifying `INDEX_VAR` within the loop is allowed, but it has no effect +on the value of `INDEX_VAR` in the next iteration. + +Specify a boolean expression for the condition on `LOOP` to cause the +loop to be executed only if the condition is true. If the condition +is false or missing before the loop contents are executed the first +time, the loop contents are not executed at all. + +If index and condition clauses are both present on `LOOP`, the index +variable is always set before the condition is evaluated. Thus, a +condition that makes use of the index variable will always see the index +value to be used in the next execution of the body. + +Specify a boolean expression for the condition on `END LOOP` to cause +the loop to terminate if the condition is true after the enclosed code +block is executed. The condition is evaluated at the end of the loop, +not at the beginning, so that the body of a loop with only a condition +on `END LOOP` will always execute at least once. + +If the index clause is not present, then the global `MXLOOPS` +setting, which defaults to 40, limits the number of iterations (*note +SET MXLOOPS::). + +[`BREAK`](break.md) also terminates `LOOP` execution. + +Loop index variables are by default reset to system-missing from one +case to another, not left, unless a scratch variable is used as index. +When loops are nested, this is usually undesired behavior, which can +be corrected with [`LEAVE`](../../commands/variables/leave.md) or by +using a [scratch +variable](../../language/datasets/scratch-variables.md) as the loop +index. + +When `LOOP` or `END LOOP` is specified following +[`TEMPORARY`](../../commands/selection/temporary.md), the +[`LAG`](../../language/expressions/functions/miscellaneous.md) +function may not be used. + diff --git a/rust/doc/src/language/expressions/functions/miscellaneous.md b/rust/doc/src/language/expressions/functions/miscellaneous.md index cac7c3b932..76eb0bf99b 100644 --- a/rust/doc/src/language/expressions/functions/miscellaneous.md +++ b/rust/doc/src/language/expressions/functions/miscellaneous.md @@ -1,6 +1,6 @@ # Miscellaneous Functions -* `LAG (VARIABLE[, N])` +* `LAG(VARIABLE[, N])` `VARIABLE` must be a numeric or string variable name. `LAG` yields the value of that variable for the case `N` before the current one. Results in system-missing (for numeric variables) or blanks (for @@ -21,7 +21,7 @@ positive constant integer. There is no explicit limit, but use of a large value will increase memory consumption. -* `YRMODA (YEAR, MONTH, DAY)` +* `YRMODA(YEAR, MONTH, DAY)` YEAR is a year, either between 0 and 99 or at least 1582. Unlike other PSPP date functions, years between 0 and 99 always correspond to 1900 through 1999. `MONTH` is a month between 1 and 13. `DAY` @@ -34,7 +34,7 @@ date specified, plus one. The date passed to `YRMODA` must be on or after 15 Oct 1582. 15 Oct 1582 has a value of 1. -* `VALUELABEL (VARIABLE)` +* `VALUELABEL(VARIABLE)` Returns a string matching the label associated with the current value of `VARIABLE`. If the current value of `VARIABLE` has no associated label, then this function returns the empty string. diff --git a/rust/doc/src/language/expressions/functions/missing-value.md b/rust/doc/src/language/expressions/functions/missing-value.md index 613e895654..c0e86bec4b 100644 --- a/rust/doc/src/language/expressions/functions/missing-value.md +++ b/rust/doc/src/language/expressions/functions/missing-value.md @@ -6,7 +6,7 @@ of evaluation apply within expression arguments to these functions. In particular, user-missing values for numeric variables are converted to system-missing values. -* `MISSING (EXPR)` +* `MISSING(EXPR)` When `EXPR` is simply the name of a numeric variable, returns 1 if the variable has the system-missing value or if it is user-missing. For any other value 0 is returned. If `EXPR` is any other kind of diff --git a/rust/doc/src/language/expressions/functions/time-and-date.md b/rust/doc/src/language/expressions/functions/time-and-date.md index 6bf5ed81ef..863d060057 100644 --- a/rust/doc/src/language/expressions/functions/time-and-date.md +++ b/rust/doc/src/language/expressions/functions/time-and-date.md @@ -34,10 +34,10 @@ given below correspond with the numeric PSPP dates given: These functions take numeric arguments and return numeric values that represent times. -* `TIME.DAYS (NDAYS)` +* `TIME.DAYS(NDAYS)` Returns a time corresponding to NDAYS days. -* `TIME.HMS (NHOURS, NMINS, NSECS)` +* `TIME.HMS(NHOURS, NMINS, NSECS)` Returns a time corresponding to NHOURS hours, NMINS minutes, and NSECS seconds. The arguments may not have mixed signs: if any of them are positive, then none may be negative, and vice versa. @@ -47,16 +47,16 @@ represent times. These functions take numeric arguments in PSPP time format and give numeric results. -* `CTIME.DAYS (TIME)` +* `CTIME.DAYS(TIME)` Results in the number of days and fractional days in TIME. -* `CTIME.HOURS (TIME)` +* `CTIME.HOURS(TIME)` Results in the number of hours and fractional hours in TIME. -* `CTIME.MINUTES (TIME)` +* `CTIME.MINUTES(TIME)` Results in the number of minutes and fractional minutes in TIME. -* `CTIME.SECONDS (TIME)` +* `CTIME.SECONDS(TIME)` Results in the number of seconds and fractional seconds in TIME. (`CTIME.SECONDS` does nothing; `CTIME.SECONDS(X)` is equivalent to `X`.) @@ -96,24 +96,24 @@ If these functions' arguments are out-of-range, they are correctly normalized before conversion to date format. Non-integers are rounded toward zero. -* `DATE.DMY (DAY, MONTH, YEAR)` - `DATE.MDY (MONTH, DAY, YEAR)` +* `DATE.DMY(DAY, MONTH, YEAR)` + `DATE.MDY(MONTH, DAY, YEAR)` Results in a date value corresponding to the midnight before day DAY of month MONTH of year YEAR. -* `DATE.MOYR (MONTH, YEAR)` +* `DATE.MOYR(MONTH, YEAR)` Results in a date value corresponding to the midnight before the first day of month MONTH of year YEAR. -* `DATE.QYR (QUARTER, YEAR)` +* `DATE.QYR(QUARTER, YEAR)` Results in a date value corresponding to the midnight before the first day of quarter QUARTER of year YEAR. -* `DATE.WKYR (WEEK, YEAR)` +* `DATE.WKYR(WEEK, YEAR)` Results in a date value corresponding to the midnight before the first day of week WEEK of year YEAR. -* `DATE.YRDAY (YEAR, YDAY)` +* `DATE.YRDAY(YEAR, YDAY)` Results in a date value corresponding to the day YDAY of year YEAR. ## Examining Dates @@ -132,59 +132,59 @@ give numeric results. These names are used for arguments: The functions for examining dates are: -* `XDATE.DATE (TIME-OR-DATE)` +* `XDATE.DATE(TIME-OR-DATE)` For a time, results in the time corresponding to the number of whole days DATE-OR-TIME includes. For a date, results in the date corresponding to the latest midnight at or before DATE-OR-TIME; that is, gives the date that DATE-OR-TIME is in. -* `XDATE.HOUR (TIME-OR-DATE)` +* `XDATE.HOUR(TIME-OR-DATE)` For a time, results in the number of whole hours beyond the number of whole days represented by DATE-OR-TIME. For a date, results in - the hour (as an integer between 0 and 23) corresponding to + the hour(as an integer between 0 and 23) corresponding to DATE-OR-TIME. -* `XDATE.JDAY (DATE)` +* `XDATE.JDAY(DATE)` Results in the day of the year (as an integer between 1 and 366) corresponding to DATE. -* `XDATE.MDAY (DATE)` +* `XDATE.MDAY(DATE)` Results in the day of the month (as an integer between 1 and 31) corresponding to DATE. -* `XDATE.MINUTE (TIME-OR-DATE)` +* `XDATE.MINUTE(TIME-OR-DATE)` Results in the number of minutes (as an integer between 0 and 59) after the last hour in TIME-OR-DATE. -* `XDATE.MONTH (DATE)` +* `XDATE.MONTH(DATE)` Results in the month of the year (as an integer between 1 and 12) corresponding to DATE. -* `XDATE.QUARTER (DATE)` +* `XDATE.QUARTER(DATE)` Results in the quarter of the year (as an integer between 1 and 4) corresponding to DATE. -* `XDATE.SECOND (TIME-OR-DATE)` +* `XDATE.SECOND(TIME-OR-DATE)` Results in the number of whole seconds after the last whole minute (as an integer between 0 and 59) in TIME-OR-DATE. -* `XDATE.TDAY (DATE)` +* `XDATE.TDAY(DATE)` Results in the number of whole days from 14 Oct 1582 to DATE. -* `XDATE.TIME (DATE)` +* `XDATE.TIME(DATE)` Results in the time of day at the instant corresponding to DATE, as a time value. This is the number of seconds since midnight on the day corresponding to DATE. -* `XDATE.WEEK (DATE)` +* `XDATE.WEEK(DATE)` Results in the week of the year (as an integer between 1 and 53) corresponding to DATE. -* `XDATE.WKDAY (DATE)` +* `XDATE.WKDAY(DATE)` Results in the day of week (as an integer between 1 and 7) corresponding to DATE, where 1 represents Sunday. -* `XDATE.YEAR (DATE)` +* `XDATE.YEAR(DATE)` Returns the year (as an integer 1582 or greater) corresponding to DATE. @@ -208,7 +208,7 @@ analysis. PSPP supplies a few functions for date arithmetic: -* `DATEDIFF (DATE2, DATE1, UNIT)` +* `DATEDIFF(DATE2, DATE1, UNIT)` Returns the span of time from `DATE1` to `DATE2` in terms of `UNIT`, which must be a quoted string, one of `years`, `quarters`, `months`, `weeks`, `days`, `hours`, `minutes`, and `seconds`. The result is @@ -222,7 +222,7 @@ analysis. day of the following month. Thus, there is never a full month from Jan. 31 of a given year to any day in the following February. -* `DATESUM (DATE, QUANTITY, UNIT[, METHOD])` +* `DATESUM(DATE, QUANTITY, UNIT[, METHOD])` Returns `DATE` advanced by the given `QUANTITY` of the specified `UNIT`, which must be one of the strings `years`, `quarters`, `months`, `weeks`, `days`, `hours`, `minutes`, and `seconds`.