.. _skoolMacros: Skool macros ============ Skool files and ref files may contain skool macros that are 'expanded' to an appropriate piece of HTML markup (when rendering in HTML mode), or to an appropriate piece of plain text (when rendering in ASM mode). Syntax ^^^^^^ Skool macros have the following general form:: #MACROri1,ri2,...[,oi1,oi2,...](rs1,rs2,...[,os1,os2,...]) where: * ``MACRO`` is the macro name * ``ri1``, ``ri2`` etc. are required integer parameters * ``oi1``, ``oi2`` etc. are optional integer parameters * ``rs1``, ``rs2`` etc. are required string parameters * ``os1``, ``os2`` etc. are optional string parameters If an optional parameter is left blank or omitted entirely, it assumes its default value. So, for example:: #UDG39144 is equivalent to:: #UDG39144,56,4,1,0,0,0,1 and:: #UDG30115,,2 is equivalent to:: #UDG30115,56,2 .. _numericParameters: Numeric parameters ^^^^^^^^^^^^^^^^^^ Numeric parameters may be written in decimal notation:: #UDG51673,17 or in hexadecimal notation (prefixed by ``$``):: #UDG$C9D9,$11 Wherever a sequence of numeric parameters appears in a macro, that sequence may optionally be enclosed in parentheses: ``(`` and ``)``. Parentheses are generally recommended - especially when there are two or more parameters - in order to unambiguously separate the numeric parameters from any content that follows them. Parentheses are `required` if any numeric parameter is written as an expression containing arithmetic operations, skool macros or replacement fields:: #UDG(51672+{offset},#PEEK51672) The following operators are permitted in an arithmetic expression: * arithmetic operators: ``+``, ``-``, ``*``, ``/``, ``%`` (modulo), ``**`` (power) * bitwise operators: ``&`` (AND), ``|`` (OR), ``^`` (XOR) * bit shift operators: ``>>``, ``<<`` * Boolean operators: ``&&`` (and), ``||`` (or) * comparison operators: ``==``, ``!=``, ``>``, ``<``, ``>=``, ``<=`` Parentheses and spaces are also permitted in an arithmetic expression:: #IF(1 == 2 || (1 <= 2 && 2 < 3))(Yes,No) .. _stringParameters: String parameters ^^^^^^^^^^^^^^^^^ Where a macro requires a single string parameter consisting of arbitrary text, it must be enclosed in parentheses, square brackets or braces:: (text) [text] {text} If ``text`` contains unbalanced brackets, a non-whitespace character that is not present in ``text`` may be used as an alternative delimiter. For example:: /text/ |text| Where a macro requires multiple string parameters consisting of arbitrary text, they must be enclosed in parentheses, square brackets or braces and be separated by commas:: (string1,string2) [string1,string2] {string1,string2} When a comma-separated sequence of string parameters is split, any commas that appear between parentheses are retained. For example, the string parameters of the outer ``#FOR`` macro in:: #FOR0,1(n,#FOR(0,1)(m,(n,m),;),;) are split into ``n``, ``#FOR(0,1)(m,(n,m),;)`` and ``;``, and the string parameters of the inner ``#FOR`` macro are split into ``m``, ``(n,m)``, and ``;``. Alternatively, an arbitrary delimiter - ``d``, which cannot be whitespace - and separator - ``s``, which can be whitespace - may be used. (They can be the same character.) The string parameters must open with ``ds``, be separated by ``s``, and close with ``sd``. For example:: //same/delimiter/and/separator// | different delimiter and separator | Note that if an alternative delimiter or separator is used, it must not be '&', '<' or '>'. .. versionchanged:: 6.4 When a comma-separated sequence of string parameters is split, any commas that appear between parentheses are retained. .. _replacementFields: Replacement fields ^^^^^^^^^^^^^^^^^^ The following replacement fields are available for use in the integer parameters of the :ref:`asm-if` directive and every skool macro (including macros defined by :ref:`DEF`), and also in the string parameters of some macros: * ``asm`` - 1 if in :ref:`isubMode`, 2 if in :ref:`ssubMode`, 3 if in :ref:`rsubMode`, or 0 otherwise * ``base`` - 10 if the ``--decimal`` option is used with :ref:`skool2asm.py` or :ref:`skool2html.py`, 16 if the ``--hex`` option is used, or 0 if neither option is used * ``case`` - 1 if the ``--lower`` option is used with :ref:`skool2asm.py` or :ref:`skool2html.py`, 2 if the ``--upper`` option is used, or 0 if neither option is used * ``fix`` - 1 if in :ref:`ofixMode`, 2 if in :ref:`bfixMode`, 3 if in :ref:`rfixMode`, or 0 otherwise * ``html`` - 1 if in HTML mode, 0 otherwise * ``mode`` - a dictionary containing a copy of the ``asm``, ``base``, ``case``, ``fix`` and ``html`` fields * ``sim`` - a dictionary of register values populated by the :ref:`SIM` macro * ``vars`` - a dictionary of variables defined by the ``--var`` option of :ref:`skool2asm.py` or :ref:`skool2html.py`; accessing an undefined variable in this dictionary yields the integer value '0' Replacement fields for the variables defined by the :ref:`LET` macro are also available. Note that the ``#LET`` macro can change the values of the ``asm``, ``base``, ``case``, ``fix`` and ``html`` fields, but their original values are always available in the ``mode`` dictionary. For example:: #IF({mode[case]}==1)(hl,HL) expands to ``hl`` if in lower case mode, or ``HL`` otherwise. Note that if a replacement field is used, the parameter string must be enclosed in parentheses. .. versionchanged:: 8.7 Added the ``sim`` dictionary. .. versionchanged:: 8.2 Added the ``mode`` dictionary. .. versionchanged:: 6.4 The ``asm`` replacement field indicates the exact ASM mode; added the ``fix`` and ``vars`` replacement fields. .. _SMPLmacros: SMPL macros ^^^^^^^^^^^ The macros described in this section constitute the Skool Macro Programming Language (SMPL). They can be used to programmatically specify values in the parameter string of any macro. .. _hash: #() --- The ``#()`` macro expands the skool macros in its sole string parameter. :: #(text) It takes effect only when it immediately follows the opening token of another skool macro, and is expanded `before` that macro. For example:: #UDGARRAY#(2(#FOR37159,37168,9||n|(n+1),#PEEKn|;||))(item) This instance of the ``#()`` macro expands the ``#FOR`` macro first, giving:: 2((37159+1),#PEEK37159;(37168+1),#PEEK37168) It then expands the ``#PEEK`` macros, ultimately forming the parameters of the ``#UDGARRAY`` macro. See :ref:`stringParameters` for details on alternative ways to supply the ``text`` parameter. Note that if an alternative delimiter is used, it must not be an alphanumeric character (A-Z, a-z, 0-9). .. _DEF: #DEF ---- The ``#DEF`` macro defines a new skool macro. :: #DEF[flags](#MACRO[(ia[=i0],ib[=i1]...)[(sa[=s0],sb[=s1]...)]] body) * ``flags`` controls various options (see below) * ``MACRO`` is the macro name (which must be all upper case letters) * ``ia[=i0]``, ``ib[=i1]`` etc. are the integer parameter names and optional default values; the parameter names must consist of lower case letters only * ``sa[=s0]``, ``sb[=s1]`` etc. are the string parameter names and optional default values * ``body`` is the body of the macro definition, which may contain placeholders (``$var``, ``${var}`` - when ``flags`` is 0) or replacement fields (``{var}`` - when ``flags`` is 1) representing the integer and string argument values ``flags`` is the sum of the following values, chosen according to the desired outcome: * 1 - use replacement fields (e.g. ``{var}``) instead of $-placeholders (``$var``, ``${var}``) to represent the defined macro's argument values * 2 - strip leading and trailing whitespace from the output of the defined macro whenever it is expanded For example:: #DEF(#MIN(a,b) #IF($a<$b)($a,$b)) This defines a ``#MIN`` macro that accepts two integer arguments and expands to the value of the smaller argument. Default values for the defined macro's optional integer parameters can be specified in the macro's signature. For example:: #DEF(#PROD(a,b=1,c=1) #EVAL($a*$b*$c)) This defines a ``#PROD`` macro that accepts one, two or three integer arguments, the second and third of which default to 1, and expands to the product of all three arguments. Default values for the defined macro's optional string parameters can also be specified in the macro's signature, and their default values may refer to the integer argument values. For example:: #DEF(#NUM(a)(s=$a) $s) This defines a ``#NUM`` macro that accepts one integer argument and an optional string argument. It expands either to the integer argument, or to the string argument if provided. So ``#NUM15`` expands to '15', and ``#NUM15($0F)`` expands to '$0F'. If ``flags`` is odd (bit 0 set), replacement fields are used instead of $-placeholders to represent the defined macro's argument values. The main advantage of using replacement fields is that Python string formatting options can be used on the argument values. For example:: #DEF1(#HEX(n) {n:04X}) This defines a ``#HEX`` macro that formats its sole integer argument as a 4-digit upper case hexadecimal number. However, when using replacement fields, care must be taken to escape any field that doesn't represent an argument value. For example:: #LET(count=0) #DEF1(#ADD(amount) #LET(count={{count}}+{amount})) This defines a variable named ``count``, and an ``#ADD`` macro that increases its value by a given amount. Note how the replacement field for the ``count`` variable in the body of the macro definition is escaped: ``{{count}}``. If bit 1 of ``flags`` is set, the defined macro will be expanded, in isolation from any surrounding content, as soon as it is encountered. For that to work, the macro definition must be entirely self-contained, i.e. it must not depend on any surrounding content in order to be syntactically correct. For example, if the ``#IFZERO`` macro is defined thus:: #DEF2(#IFZERO(n) #IF($n==0)) then any attempt to expand an ``#IFZERO`` macro will lead to an error message about the ``#IF`` macro having no output strings. To fix this, either reset bit 1 of ``flags``, or redefine ``#IFZERO`` with the output strings included in the definition:: #DEF2(#IFZERO(n)(a,b) #IF($n==0)($a,$b)) For more examples, see :ref:`definingMacrosWithDEF`. Note that if a string parameter of a defined macro is optional, that argument will take its default value only if it is omitted; if instead it is left blank, it takes the value of the empty string. In general, the string arguments of a defined macro may be supplied between alternative delimiters (see :ref:`stringParameters`) if desired. However, if every string parameter of the defined macro is optional, the string arguments must be either omitted entirely or provided between parentheses (and therefore separated by commas). This allows a macro with all of its optional string arguments omitted to be immediately followed by some character other than an opening parenthesis without that character being interpreted as an alternative delimiter. To define a macro that will be available for use immediately anywhere in the skool file or ref files, consider using the :ref:`expand` directive, or the ``Expand`` parameter in the :ref:`ref-Config` section. The ``flags`` parameter of the ``#DEF`` macro may contain :ref:`replacement fields `. The integer parameters of a macro defined by ``#DEF`` may contain :ref:`replacement fields `, and may also be supplied via keyword arguments. +---------+------------------------------------------------------------------+ | Version | Changes | +=========+==================================================================+ | 8.6 | Added the ``flags`` parameter, the ability to use replacement | | | fields to represent the defined macro's argument values, and the | | | ability to strip whitespace from the defined macro's output | +---------+------------------------------------------------------------------+ | 8.5 | New | +---------+------------------------------------------------------------------+ .. _EVAL: #EVAL ----- The ``#EVAL`` macro expands to the value of an arithmetic expression. :: #EVALexpr[,base,width] * ``expr`` is the arithmetic expression * ``base`` is the number base in which the value is expressed: 2, 10 (the default) or 16 * ``width`` is the minimum number of digits in the output (default: 1); the value will be padded with leading zeroes if necessary For example:: ; The following mask byte is #EVAL(#PEEK29435,2,8). 29435 DEFB 62 This instance of the ``#EVAL`` macro expands to '00111110' (62 in binary). The parameter string of the ``#EVAL`` macro may contain :ref:`replacement fields `. +---------+-------------------------------------------------------------------+ | Version | Changes | +=========+===================================================================+ | 8.0 | Added support for replacement fields in the parameter string | +---------+-------------------------------------------------------------------+ | 6.0 | Hexadecimal values are rendered in lower case when the | | | ``--lower`` option is used | +---------+-------------------------------------------------------------------+ | 5.1 | New | +---------+-------------------------------------------------------------------+ .. _FOR: #FOR ---- The ``#FOR`` macro expands to a sequence of strings based on a range of integers. :: #FORstart,stop[,step,flags](var,string[,sep,fsep]) * ``start`` is first integer in the range * ``stop`` is the final integer in the range * ``step`` is the gap between each integer in the range (default: 1) * ``flags`` controls whether to affix commas to or replace variable names in each separator (see below) * ``var`` is the variable name; for each integer in the range, it evaluates to that integer * ``string`` is the output string that is evaluated for each integer in the range; wherever the variable name (``var``) appears, its value is substituted * ``sep`` is the separator placed between each output string (default: the empty string); this may be modified depending on the value of ``flags`` * ``fsep`` is the separator placed between the final two output strings (default: ``sep``) ``flags`` is the sum of the following values, chosen according to the desired outcome: * 1 - prefix each separator (``sep``) with a comma * 2 - suffix each separator (``sep``) with a comma * 4 - replace any variable name (``var``) in each separator (``sep``) with the variable value For example:: ; The next three bytes (#FOR31734,31736,,1(n,#PEEKn, , and )) define the ; item locations. 31734 DEFB 24,17,156 This instance of the ``#FOR`` macro expands to '24, 17 and 156'. The integer parameters of the ``#FOR`` macro (``start``, ``stop``, ``step``, ``flags``) may contain :ref:`replacement fields `. See :ref:`stringParameters` for details on alternative ways to supply the ``var``, ``string``, ``sep`` and ``fsep`` parameters. +---------+-------------------------------------------------------------------+ | Version | Changes | +=========+===================================================================+ | 8.7 | Added the ``flags`` parameter | +---------+-------------------------------------------------------------------+ | 8.2 | Added support for replacement fields in the integer parameters | +---------+-------------------------------------------------------------------+ | 5.1 | New | +---------+-------------------------------------------------------------------+ .. _FOREACH: #FOREACH -------- The ``#FOREACH`` macro expands to a sequence of output strings based on a sequence of input strings. :: #FOREACH([s1,s2,...])(var,string[,sep,fsep]) or:: #FOREACH(svar)(var,string[,sep,fsep]) * ``s1``, ``s2`` etc. are the input strings * ``svar`` is a special variable that expands to a specific sequence of input strings (see below) * ``var`` is the variable name; for each input string, it evaluates to that string * ``string`` is the output string that is evaluated for each input string; wherever the variable name (``var``) appears, its value is substituted * ``sep`` is the separator placed between each output string (default: the empty string) * ``fsep`` is the separator placed between the final two output strings (default: ``sep``) For example:: ; The next three bytes (#FOREACH(31734,31735,31736)||n|#PEEKn|, | and ||) ; define the item locations. 31734 DEFB 24,17,156 This instance of the ``#FOREACH`` macro expands to '24, 17 and 156'. The ``#FOREACH`` macro recognises certain special variables, each one of which expands to a specific sequence of strings. The special variables are: * ``ENTRY[types]`` - the addresses of every entry of the specified type(s) in the memory map; if ``types`` is not given, every type is included * ``EREFaddr`` - the addresses of the routines that jump to or call a given instruction (at ``addr``) * ``REFaddr`` - the addresses of the routines that jump to or call a given routine (at ``addr``), or jump to or call any entry point within that routine For example:: ; The messages can be found at #FOREACH(ENTRYt)||n|n|, | and ||. This instance of the ``#FOREACH`` macro expands to a list of the addresses of the entries of type ``t`` (text). See :ref:`stringParameters` for details on alternative ways to supply the ``s1,s2,...`` and ``var,string[,sep,fsep]`` parameter strings. +---------+---------+ | Version | Changes | +=========+=========+ | 5.1 | New | +---------+---------+ .. _FORMAT: #FORMAT ------- The ``#FORMAT`` macro performs a Python-style `string formatting operation`_ on its string argument. :: #FORMAT[case](text) * ``case`` is 1 to convert the formatted string to lower case, 2 to convert it to upper case, or 0 to leave it alone (the default) * ``text`` is the string to format For example:: #FORMAT(0x{count:04X}) This instance of the ``#FORMAT`` macro formats the value of the ``count`` variable (assuming it has already been defined by the :ref:`LET` macro) as a 4-digit upper case hexadecimal number prefixed by '0x'. Note that if ``text`` could be read as an integer parameter, ``case`` should be explicitly specified in order to prevent ``text`` from being interpreted as the ``case`` parameter. For example:: #FORMAT0({count}) Alternatively, the :ref:`EVAL` macro may be a better option for formatting a pure numeric value. The parameters of the ``#FORMAT`` macro may contain :ref:`replacement fields `. See :ref:`stringParameters` for details on alternative ways to supply the ``text`` parameter. +---------+-------------------------------------------------------------------+ | Version | Changes | +=========+===================================================================+ | 8.5 | Added the ``case`` parameter | +---------+-------------------------------------------------------------------+ | 8.2 | New | +---------+-------------------------------------------------------------------+ .. _string formatting operation: https://docs.python.org/3/library/string.html#format-string-syntax .. _IF: #IF --- The ``#IF`` macro expands to an arbitrary string based on the truth value of an arithmetic expression. :: #IFexpr(true[,false]) * ``expr`` is the arithmetic expression, which may contain :ref:`replacement fields ` * ``true`` is the output string when ``expr`` is true * ``false`` is the output string when ``expr`` is false (default: the empty string) For example:: ; #FOR0,7(n,#IF(#PEEK47134 & 2**(7-n))(X,O)) 47134 DEFB 170 This instance of the ``#IF`` macro is used (in combination with a ``#FOR`` macro and a ``#PEEK`` macro) to display the contents of the address 47134 in the memory snapshot in binary format with 'X' for one and 'O' for zero: XOXOXOXO. See :ref:`stringParameters` for details on alternative ways to supply the ``true`` and ``false`` output strings. +---------+----------------------------------------------------------------+ | Version | Changes | +=========+================================================================+ | 6.0 | Added support for replacement fields in the ``expr`` parameter | +---------+----------------------------------------------------------------+ | 5.1 | New | +---------+----------------------------------------------------------------+ .. _LET: #LET ---- The ``#LET`` macro defines an integer, string or dictionary variable. The syntax for defining an integer or string variable is:: #LET(name=value) * ``name`` is the variable name * ``value`` is the value to assign; this may contain skool macros (which are expanded immediately) and :ref:`replacement fields ` (which are replaced after any skool macros have been expanded) If ``name`` ends with a dollar sign (``$``), ``value`` is interpreted as a string. Otherwise ``value`` is evaluated as an arithmetic expression. For example:: #LET(count=2*2) #LET(count$=2*2) These ``#LET`` macros assign the integer value '4' to the variable ``count`` and the string value '2*2' to the variable ``count$``. The variables are then accessible to other macros via the replacement fields ``{count}`` and ``{count$}``. The syntax for defining a dictionary variable is:: #LET(name[]=(default[,k1[:v1],k2[:v2]...])) * ``name`` is the dictionary variable name * ``default`` is the default value (used when a key is not found in the dictionary) * ``k1:v1``, ``k2:v2`` etc. are the key-value pairs in the dictionary The keys in a dictionary are integers, and the associated values are strings if ``name`` ends with a dollar sign, or integers otherwise. If the value part of a key-value pair is omitted, it defaults to the key. For example:: #LET(n[]=(0,1:10,2:20)) #LET(d$[]=(?,1:a,2:b)) The first ``#LET`` macro defines the dictionary variable ``n`` with default integer value 0, and keys '1' and '2' mapping to the integer values 10 and 20. The values in this dictionary are accessible to other macros via the replacement fields ``{n[1]}`` and ``{n[2]}``. The second ``#LET`` macro defines the dictionary variable ``d$`` with default string value '?', and keys '1' and '2' mapping to the string values 'a' and 'b'. The values in this dictionary are accessible to other macros via the replacement fields ``{d$[1]}`` and ``{d$[2]}``. To define a variable that will be available for use immediately anywhere in the skool file or ref files, consider using the :ref:`expand` directive. See :ref:`stringParameters` for details on alternative ways to supply the entire ``name=value`` parameter string, or the part after the equals sign when defining a dictionary variable. +---------+--------------------------------------------------+ | Version | Changes | +=========+==================================================+ | 8.6 | Added the ability to define dictionary variables | +---------+--------------------------------------------------+ | 8.2 | New | +---------+--------------------------------------------------+ .. _MAP: #MAP ---- The ``#MAP`` macro expands to a value from a map of key-value pairs whose keys are integers. :: #MAPkey(default[,k1:v1,k2:v2...]) * ``key`` is the integer to look up in the map; this parameter may contain :ref:`replacement fields ` * ``default`` is the default output string (used when ``key`` is not found in the map) * ``k1:v1``, ``k2:v2`` etc. are the key-value pairs in the map For example:: ; The next three bytes specify the directions that are available from here: ; #FOR56112,56114,,1(q,#MAP(#PEEKq)(?,0:left,1:right,2:up,3:down), , and ). 56112 DEFB 0,1,3 This instance of the ``#MAP`` macro is used (in combination with a ``#FOR`` macro and a ``#PEEK`` macro) to display a list of directions available based on the contents of addresses 56112-56114: 'left, right and down'. Note that the keys (``k1``, ``k2`` etc.) may be expressed using arithmetic operations. They may also be expressed using skool macros, but in that case the *entire* parameter string of the ``#MAP`` macro must be enclosed by a :ref:`hash` macro. See :ref:`stringParameters` for details on alternative ways to supply the default output string and the key-value pairs. +---------+---------------------------------------------------------------+ | Version | Changes | +=========+===============================================================+ | 6.0 | Added support for replacement fields in the ``key`` parameter | +---------+---------------------------------------------------------------+ | 5.1 | New | +---------+---------------------------------------------------------------+ .. _PC: #PC --- The ``#PC`` macro expands to the address of the closest instruction in the current entry. :: #PC For example:: c32768 XOR A ; This instruction is at #PC. This instance of the ``#PC`` macro expands to '32768'. In an entry header (i.e. title, description, register description or start comment), the ``#PC`` macro expands to the address of the first instruction in the entry. In a mid-block comment, the ``#PC`` macro expands to the address of the following instruction. In an instruction-level comment, the ``#PC`` macro expands to the address of the instruction. In a block end comment, the ``#PC`` macro expands to the address of the last instruction in the entry. +---------+---------+ | Version | Changes | +=========+=========+ | 8.0 | New | +---------+---------+ .. _PEEK: #PEEK ----- The ``#PEEK`` macro expands to the contents of an address in the internal memory snapshot constructed from the contents of the skool file. :: #PEEKaddr * ``addr`` is the address, which may contain :ref:`replacement fields ` For example:: ; At the start of the game, the number of lives remaining is #PEEK33879. This instance of the ``#PEEK`` macro expands to the contents of the address 33879 in the internal memory snapshot. See also :ref:`POKES`. +---------+-------------------------------------------------------------------+ | Version | Changes | +=========+===================================================================+ | 8.2 | Added support for replacement fields in the ``addr`` parameter | +---------+-------------------------------------------------------------------+ | 5.1 | New | +---------+-------------------------------------------------------------------+ .. _STR: #STR ---- The ``#STR`` macro expands to the text string at a given address in the memory snapshot. :: #STRaddr[,flags,length][(end)] * ``addr`` is the address of the first character in the string * ``flags`` indicates operations to be performed on the string (default: 0) * ``length`` is the number of characters in the string; if -1 (the default), the string ends immediately before the first zero byte, or on the first byte that has bit 7 set (bit 7 of that byte will be reset before converting it to a character), or when ``end`` evaluates to true * ``end`` is an arithmetic expression that identifies the end marker byte for the string (when bit 3 of ``flags`` is set) ``flags`` is the sum of the following values, chosen according to the desired outcome: * 1 - strip trailing whitespace from the string * 2 - strip leading whitespace from the string * 4 - replace each sequence of N>=2 spaces in the string with ``#SPACE(N)`` (see :ref:`SPACE`) * 8 - use the ``end`` parameter to determine where the string ends When bit 3 of ``flags`` is set, ``end`` is evaluated for each byte encountered, and if the result is true, the string is terminated. ``end`` may contain the placeholder ``$b`` for the current byte value. For example:: ; The messages here are '#STR47154', '#STR47158' and '#STR47161,8($b==255)'. 47154 DEFM "One",0 47158 DEFM "Tw","o"+128 47161 DEFM "Three",255 These instances of the ``#STR`` macro expand to 'One', 'Two' and 'Three'. The parameters of the ``#STR`` macro may contain :ref:`replacement fields `. +---------+---------+ | Version | Changes | +=========+=========+ | 8.6 | New | +---------+---------+ .. _WHILE: #WHILE ------ The ``#WHILE`` macro repeatedly expands macros while a conditional expression is true. :: #WHILE(expr)(body) * ``expr`` is the conditional expression * ``body`` is the text to repeatedly expand; leading and trailing whitespace are stripped from the expanded value For example:: #LET(a=3) #WHILE({a}>0)( #EVAL({a}) #LET(a={a}-1) ) This instance of the ``#WHILE`` macro expands to '321'. The ``expr`` parameter of the ``#WHILE`` macro may contain :ref:`replacement fields `. See :ref:`stringParameters` for details on alternative ways to supply the ``body`` parameter. +---------+---------+ | Version | Changes | +=========+=========+ | 8.6 | New | +---------+---------+ General macros ^^^^^^^^^^^^^^ .. _AUDIO: #AUDIO ------ In HTML mode, the ``#AUDIO`` macro expands to an HTML5 ``