work on manual
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 7 May 2025 20:56:17 +0000 (13:56 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 7 May 2025 20:56:17 +0000 (13:56 -0700)
rust/doc/src/SUMMARY.md
rust/doc/src/commands/control/break.md [new file with mode: 0644]
rust/doc/src/commands/control/define.md [new file with mode: 0644]
rust/doc/src/commands/control/do-if.md [new file with mode: 0644]
rust/doc/src/commands/control/do-repeat.md [new file with mode: 0644]
rust/doc/src/commands/control/index.md [new file with mode: 0644]
rust/doc/src/commands/control/loop.md [new file with mode: 0644]
rust/doc/src/language/expressions/functions/miscellaneous.md
rust/doc/src/language/expressions/functions/missing-value.md
rust/doc/src/language/expressions/functions/time-and-date.md

index 8e9467813baef7a15eafff607a45997af50e6629..5d589f80c1730dc745637ef39640ae8f6d00f10a 100644 (file)
   - [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 (file)
index 0000000..866a703
--- /dev/null
@@ -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 (file)
index 0000000..eddfa20
--- /dev/null
@@ -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`|
+
+* <a name="eval">`!EVAL(arg)`</a>  
+     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 (file)
index 0000000..b6b2022
--- /dev/null
@@ -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 (file)
index 0000000..20b029f
--- /dev/null
@@ -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 (file)
index 0000000..3b005f9
--- /dev/null
@@ -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 (file)
index 0000000..6e98418
--- /dev/null
@@ -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.
+
index cac7c3b9324aeb86d71a1b6bee52993671105775..76eb0bf99be5835f2e678db3b52bc47e692aa7f0 100644 (file)
@@ -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.
index 613e895654a4d514522ea9d6df0f337ad70077d3..c0e86bec4b635b1879a6458bb0c042985397aa07 100644 (file)
@@ -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
index 6bf5ed81ef68cc4829659fc6a218c99222e477ec..863d0600573197f7419dc4b907279d481b640117 100644 (file)
@@ -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`.