DMDX Help.


Macro Definition Keyword

<MacroDefinition name text>
<md name text
>
<macro name text
>

variant:
Macro Operation  Keywords

<macro pop name, N>
<macro push name, N>
<macro set name = expression>

    M switch alternative.  As of version 4.3.0.0 of DMDX macro names can in addition to the traditional single character C can also now be dot delimited names (they must begin with and end with one).  Maximum name length (including dots) is 40 characters and the names are case insenstive.  name is the macro to define/re-define and text is the body of the macro. The push and pop operation variants operate on existing macros (stack push and pop operations to counter N) whereas the set operation will create a new macro if it didn't already exist and set it to the ASCII equivalent of the result of expression (see <set> for expression details).  Operations are detailed waaaay down below.  Note that text is not delimited as in an M switch definition if there is white space after C. Should a non-space character follow name then it will be treated as a delimiter and a closing delimiter will also have to be present at the end of text (before the closing > of the keyword) as in the original M switch, for example:
<md m+macro body+>
    Versus:
<md m macro body>

    To use a macro expansion the sequence tilde macro character is used, ~m for example. Macros can be nested within a macro definition with the use of two tilde characters, for example:
<md b body> <md m macro ~~b>
    Macro b could be re-defined later and macro m will then expand with the new definition of b.  Using variable length macro names that might be:

<md .body. body> <md .macro. macro ~~.body.>

    NOTE:- the keyword variant of macro expansion is severely limited (at least until I write some serious code to expand the keyword parser) because you can't have angle brackets nested with a keyword definition, ie you can't have keywords in a keyword macro definition, you have to use the original DMTG switch form of macro expansion. Seeing as I can't actually find a definition if the original macro switch I include one here. The macro switch takes the form:
mCDmacro bodyD

    The m character indicates that a macro definition follows. The C character defines which macro is to be defined or re-defined, valid characters are 0..9 A..Z (others probably work if they have no special meaning elsewhere). The D characters are delimiters delimiting the actual text that the macro invocation will expand to, usually plus signs are used, anything that has no special meaning elsewhere. White space between the m, C and D characters is not allowed.   Updated with a variable length macro name the example above would be:
m.name.Dmacro bodyD
    Undefined macros don't expand (the tilde and the character remain as typed) with the exception (starting in version 3.3.1.0 of DMDX) of macro S which will expand to the subject ID.  If no subject ID has been specified use of ~S without defining it beforehand will result in ~S being removed.  Sure hope people weren't relying on that but it seems like a sufficiently unusual thing that I can ignore my usual rule of not changing how earlier item files behave and instead people can use custom file names for individual subjects without editing item files or just over writing a common file all the time like this:

+1 "You or not you?" / g "picture~S";

NOTE:- You can't define macros in items that are comments (they aren't parsed so the parser doesn't see the macro definition), nor can you define a macro in the item it's expanded in as the macro expansion occurs before the item is parsed (had to go and change the examples because of that one, oops).

NOTE:- Using characters 0..9 for macros if using the <SkipDisplay> CR indicator ~ will result in unwanted macro expansion unless there is a space between the tilde and digit or ~~ is used (where the macro code will collapse it to ~).  See the dynamic item content note.
 
NOTE:- You also can't have macros across item boundaries.  The macro definition has to be in one item and a semi-colon is an end of item no matter where it occurs so you'd never be able to get one into the macro definition.  Let alone the fact that would probably bust DMDX's item_read() function...

NOTE:- Macros used inside quotes can fall afoul of RTF control words between the tilde and the macro character stopping the macro expansion code from seeing the sequence as macro use (all the more so when you're using variable length names).  This can happen if you edit an expansion sequence in Word (say by changing the macro character) and even though there's no formatting information in there Word can still stick change tracking information in there.  Should this occur retyping the whole macro sequence should fix it or do what I do and that is cut the text out into a text editor (I use TextPad but Notepad probably words as well) and then paste it back in to Word.  Opening and saving the file in Wordpad similarly strips out all the Word guff.  Macros used elsewhere in items are fine, it's only within the double quoted text field that this occurs.

NOTE:- Considerable care must be used if you're going to have a macro expand as an item number as the branching code does not expand macros when it's looking for an item to branch to.  Macros only get expanded once it is decided that an item is going to be executed.  This can have unintended consequences as I recently discovered where I was using an alpha macro (say ~A) for an item number in a subroutine but as the initial branch over the subroutine was executing I'd get a syntax error about a missing item number.  The solution was to use a numeric macro instead (say ~9) that passes the syntax requirements for the branch but at a later date when actually in use expands to the desired item number.  One could also get around it with compound item number (say +0~A).  But you can't use macros in item numbers as branch targets.

NOTE:- Macros will require a little extra attention when the Unicode code path is on.


Simpler example

    A slightly simpler yet still pretty cool use of macros emerged recently with the need to scramble and display components in the corners of a display.  Using <xyjustification 1> and using one macro to determine the definition of another this becomes a relatively straight forward item file where the components of the display (the item 1000s) are scrambled all over the place.  The <emit> keywords are used to keep track of what was eventually presented, each component item defines some macro determined by macro O allowing them to be put into the corners for presentation.  Note that we're using the tilde in two totally different contexts in these examples, where it occurs at the start of an item it's a <SkipDisplay> indicator, elsewhere it's a macro reference (as noted above):

$~1 mO+A+;$
~1001 m~O+ "text" <emit text> +;
$~1 mO+B+;$
~1002 m~O+ g "filename" <emit filename> +;
$~1 mO+C+;$
~1003 m~O+ "more text" <emit more text> +;
$~1 mO+D+;$
~1004 m~O+ g "anotherfilename" <emit anotherfilename> +;
$+100 ~A <xy .25, .25> , ~B <xy .75, .25> , ~C <xy .25, .75> , ~D <xy .75, .75> * ;$


Another neat example, typing with macros

    Someone recently wanted to be able to have the subject type in their name and to be able to present it later on  (for a me-not me IAT) so I came up with this bit of macro code that gathers a name (even allows backspacing) without using the <ZillionTypedResponses> code (that wouldn't let you present the name later anyway, not without modifications to DMDX).  Even though it has since been superseded by the <Prose> keyword it is still a nice example of the surprising things you can do with macros.  Macro N is the name that's being assembled (as well as the resultant name), macros A through E track previous versions of macro N for backspacing (if they back space more than four times it'll just erase everything and start over).  Basically we do a multiway branch based on the key typed and add the relevant key to the end of macro N.  If they hit the back space we undo one level of backspacing and exit if they hit enter.  Note that <delay 0> is critical here, without it you're waiting for the default delay every character you type for the display to update.  If you don't use a <delay 0> in the parameters use a <delay 2> in item 20 instead (a delay of zero there will just get you constant display errors).

<ep> <delay 0> f1 t100000 <vm desktop> <nfb> <cr> <id keyboard> <umb>
<mpr +a> <mpr +b> <mpr +c> <mpr +d> <mpr +e> <mpr +f>
<mpr +g> <mpr +h> <mpr +i> <mpr +j> <mpr +k> <mpr +l>
<mpr +m> <mpr +n> <mpr +o> <mpr +p> <mpr +q> <mpr +r>
<mpr +s> <mpr +t> <mpr +u> <mpr +v> <mpr +w> <mpr +x>
<mpr +y> <mpr +z> <mpr +enter> <mpr +backspace> <mpr +space> <eop>

~1 mN++ mA++ mB++ mC++ mD++ mE++;

~10 mA+~B+ mB+~C+ mC+~D+ mD+~E+ mE+~N+;
+20 * "Enter your name: ~N" <mwb +A,101 +B,102 +C,103
+D,104 +E,105 +F,106 +G,107 +H,108 +I,109 +J,110 +K,111 +L,112
+M,113 +N,114 +O,115 +P,116 +Q,117 +R,118 +S,119 +T,120 +U,121
+V,122 +W,123 +X,124 +Y,125 +Z,126 +Backspace,30 +space,35 +Enter,199>;
~29 <bu -20> <! item is critical if people use a lower timeout>;
~30 mN+~D+ mE+~D+ mD+~C+ mC+~B+ mB+~A+ mA++ <bu -20>;
~35 mN+~N + <bu -10>;
~101 mN+~NA+ <bu -10>;
~102 mN+~NB+ <bu -10>;
~103 mN+~NC+ <bu -10>;
~104 mN+~ND+ <bu -10>;
~105 mN+~NE+ <bu -10>;
~106 mN+~NF+ <bu -10>;
~107 mN+~NG+ <bu -10>;
~108 mN+~NH+ <bu -10>;
~109 mN+~NI+ <bu -10>;
~110 mN+~NJ+ <bu -10>;
~111 mN+~NK+ <bu -10>;
~112 mN+~NL+ <bu -10>;
~113 mN+~NM+ <bu -10>;
~114 mN+~NN+ <bu -10>;
~115 mN+~NO+ <bu -10>;
~116 mN+~NP+ <bu -10>;
~117 mN+~NQ+ <bu -10>;
~118 mN+~NR+ <bu -10>;
~119 mN+~NS+ <bu -10>;
~120 mN+~NT+ <bu -10>;
~121 mN+~NU+ <bu -10>;
~122 mN+~NV+ <bu -10>;
~123 mN+~NW+ <bu -10>;
~124 mN+~NX+ <bu -10>;
~125 mN+~NY+ <bu -10>;
~126 mN+~NZ+ <bu -10>;
~199;

+1000 "Me or not me? ~N" *;

Setting a macro's value from a counter

    While superfluous given the new set operation (see below) in version 4.3.0.0 of DMDX this still serves as a useful example even if you would never want to use it anymore.  In a similar vein to the typed responses example you can use a looping subroutine to set a macro to a counter's value:

<vm desktop> <id "keyboard">

~01 <set c1 = random(10000)> <call 10>;
02 "macro is ~C, counter is " <apc 1> <bu -1> ;

~10 mC++ <set c99=c1>;
~99 <set c98=(c99 % 10)+100><ib 98>;
~100 mC+0~C+ <bu 110>;
~101 mC+1~C+ <bu 110>;
~102 mC+2~C+ <bu 110>;
~103 mC+3~C+ <bu 110>;
~104 mC+4~C+ <bu 110>;
~105 mC+5~C+ <bu 110>;
~106 mC+6~C+ <bu 110>;
~107 mC+7~C+ <bu 110>;
~108 mC+8~C+ <bu 110>;
~109 mC+9~C+;
~110 <set c99=c99 / 10> <bi -99, c99 .gt. 0>;
~111 <return>;

Iterating counters

    Perhaps more useful, you can use the previous subroutine to iterate over a range of counters effectively treating them as an array, here we set up 100 counters easily:

~1000 <set c1 = 1>;
~1005 <call -10>;
~1006 <set c~C = 0> <inc 1> <bi -1005, c1 .le. 100>;

    Of course using the set operation this becomes:

~1000 <set c1 = 1>;
~1005 <macro set C = c1>;
~1006 <set c~C = 0> <inc 1> <bi -1005, c1 .le. 100>;

 And on top of that you can also dispose of  the counter to control the iteration as well.  Note this is one the few times you can actually set a macro to some value and use it in the same item however the test of course has to go in the next item::

~1000 <macro set .counter. = 1>;
~1005 <set c~.counter. = 0> <macro set .counter. = ~.counter. + 1>;
~1006 <bi -1005, ~.counter. .le. 100>;

    The Probabilistic Selection Task also uses iteration to randomize a list.

 

Other neat examples

    The Dynamic Item Content help also uses macros extensively (including a neat Ultimatum game script) and the continue clock on examples use a macro to start items with a <co> and continue them with <cco>.



Macro Stack Operation  Keywords

<macro pop name, N>
<macro push name, N>

    The push and pop operations treat previously defined macro name as a stack (leftmost character being the top of the stack) and push the contents of counter N on to that macro (so it becomes the leftmost character) or pop the contents of that macro (left most character) into counter N.  Most of the time counter N will contain the ASCII decimal value of character (so for instance A is 65 and Z is 90) being pushed onto the macro or popped off.  The only real exception to this would be when characters are popped off an already empty macro in which case the counter will wind up with the value zero.  Note, be careful not to push a zero onto a macro as that will truncate the macro and you won't be able to pop it off as while we are treating the macro as a stack it's still a C character string that is NULL terminated (more on that below).  So any number of attempts to pop characters off an empty stack are legal, indeed it's the only way to see if the stack is empty (not till I add a strlen operator anyway). 

    There are countless applications of these operations, the scope of which I can only begin to touch upon.  Initially this was implemented to allow an anagram task to be coded in DMDX however in doing that I realized you could recode the macro based typing (if one wasn't already using the new <prose> keyword anyway) and I notice in scrolling down here that the setting a macro's value from a counter example could similarly be simplified.

    The principal feature of the anagram task that sets it apart from all other typing code in DMDX is that requires the set of characters that can be typed to be constantly adjusted to fit the list of letters remaining in the source word of the anagram that haven't been typed yet.  And this, as it turns out is quite a big deal.   Besides needing some method of manipulating macros so the contents can be parsed (much to my surprise that was the easy part) my first inclination was to limit characters using the macro typing example above with and only mapping the relevant characters.  I could probably have gotten away with <ZillionTypedResponses> I suppose but my mind wasn't going there at the time.  The problem with limiting the mapped characters turns out to be the <MultiwayBranch>, any character it branches on must be mapped as an input and of course that's not the case when we're dynamically switching them in and out.  So I had to use counters to control what characters can be typed.  And by the time I had that all working something was seriously awry (one of the reasons the branch diagnostics got expanded) with the backspacing.  Fortunately I realized the rather primitive back spacing in the original macro typing example (where it keeps track of the last four states of the typing) could be replaced with a couple of stack operations (pulling everything off onto another macro (now reversed) and then pushing it all back after throwing out the last character in items 2030 through 2080).  If we were using right to left typing of course one could just pop the top character off the macro.  I dimly remember a language from the late 70s called Forth that was similarly stack based and being appalled at the weird array of options it offered (like the example mentioned above popping a whole stack) but of course they make perfect sense once you start using it.  I think someone once wrote a whole OS in Forth just to prove it was actually useful.

    One thing I realized as I was writing the portion of the script that handles the backspace was that the code that keeps popping characters off one macro and onto another (items 2040 and 2050) was that you can't gaily pop characters off and push them on the other macro and then check for the zero marking the end of the string.  As soon as you push that zero onto the other string you've truncated it.  So there are tests in items 2040 and 2070 that check for the zero before pushing it onto another macro in the next item.  One's immediate instinct is to just have a one item loop that does it all, however this won't work unfortunately, even as elegant as it is. 

<ep>
<vm desktop>
<msfd 1000> <nfb> <cr> <t 100000>
<id keyboard> <!branchdiagnostics>
<eop>
0 mZ+ANAGRAM+ "start" <call 1000>;
1 mZ+ABCDEFGHIJKLMNOPQRSTUVWXYZ+ <call 1000>;
0"end"l;

~1000 mN++ <set c100=0> <set c101=0>
<umb> <mpr +enter> <mpr +backspace>
<mpr +a> <mpr +b> <mpr +c> <mpr +d> <mpr +e> <mpr +f>
<mpr +g> <mpr +h> <mpr +i> <mpr +j> <mpr +k> <mpr +l>
<mpr +m> <mpr +n> <mpr +o> <mpr +p> <mpr +q> <mpr +r>
<mpr +s> <mpr +t> <mpr +u> <mpr +v> <mpr +w> <mpr +x>
<mpr +y> <mpr +z>;
~1010 <set c65 = 0> <set c66 = 0> <set c67 = 0> <set c68 = 0> <set c69 = 0> <set c70 = 0> <set c71 = 0> <set c72 = 0> <set c73 = 0>
<set c74 = 0> <set c75 = 0> <set c76 = 0> <set c77 = 0> <set c78 = 0> <set c79 = 0> <set c80 = 0> <set c81 = 0> <set c82 = 0>
<set c83 = 0> <set c84 = 0> <set c85 = 0> <set c86 = 0> <set c87 = 0> <set c88 = 0> <set c89 = 0> <set c90 = 0>;
~1100 mQ+~Z+ mR+~N+ <! Loop through letters of anagram looking for letters used already>;
~1200 mO+~R+;
~1210 <macro pop Q, 100> <bi 2000, c100 .eq. 0> <! Looked at all of anagram>;
~1220 <macro pop O, 101> <bi 1300, c101 .eq. 0> <! Looked at all of typed>;
~1230 <bi -1220, c101 .ne. c100>;
~1235 mS++;
~1240 <macro pop R, 101> <bi 1260, c101 .eq. c100> <! Occurs in both so already used so remove from R list>;
~1250 <macro push S, 101> <bu -1240> <! Yes, if it's not in the list this will loop forever>;
~1260 mR+~R~S+ <bu -1200>;
~1300 <set c100 = c100 + 1300> <ib 100> <! Char must be valid to use>;
~1365 <set c65 = 1> <bu -1200>;
~1366 <set c66 = 1> <bu -1200>;
~1367 <set c67 = 1> <bu -1200>;
~1368 <set c68 = 1> <bu -1200>;
~1369 <set c69 = 1> <bu -1200>;
~1370 <set c70 = 1> <bu -1200>;
~1371 <set c71 = 1> <bu -1200>;
~1372 <set c72 = 1> <bu -1200>;
~1373 <set c73 = 1> <bu -1200>;
~1374 <set c74 = 1> <bu -1200>;
~1375 <set c75 = 1> <bu -1200>;
~1376 <set c76 = 1> <bu -1200>;
~1377 <set c77 = 1> <bu -1200>;
~1378 <set c78 = 1> <bu -1200>;
~1379 <set c79 = 1> <bu -1200>;
~1380 <set c80 = 1> <bu -1200>;
~1381 <set c81 = 1> <bu -1200>;
~1382 <set c82 = 1> <bu -1200>;
~1383 <set c83 = 1> <bu -1200>;
~1384 <set c84 = 1> <bu -1200>;
~1385 <set c85 = 1> <bu -1200>;
~1386 <set c86 = 1> <bu -1200>;
~1387 <set c87 = 1> <bu -1200>;
~1388 <set c88 = 1> <bu -1200>;
~1389 <set c89 = 1> <bu -1200>;
~1390 <set c90 = 1> <bu -1200>;

+2000 <delay 2> @-2 "~z", <cco> "Enter the anagram:", @2 "~N" <mwb +A,2101 +B,2102 +C,2103
+D,2104 +E,2105 +F,2106 +G,2107 +H,2108 +I,2109 +J,2110 +K,2111 +L,2112
+M,2113 +N,2114 +O,2115 +P,2116 +Q,2117 +R,2118 +S,2119 +T,2120 +U,2121
+V,2122 +W,2123 +X,2124 +Y,2125 +Z,2126 +Backspace,2030 +Enter,2199>;
~2020 <bu -2000> <! item is critical if people use a lower timeout>;
~2030 mO++ <! Backspace by pulling everything off, discarding last and putting them back>;
~2040 <macro pop N, 100> <bi -2060, c100 .eq. 0>;
~2050 <macro push O, 100> <bu -2040>;
~2060 <macro pop O, 100>;
~2070 <macro pop O, 100> <bi -1010, c100 .eq. 0>;
~2080 <macro push N, 100> <bu -2070>;
~2101 <bi -2000, c65 .eq. 0>;
~1 mN+~NA+ <bu -1010>;
~2102 <bi -2000, c66 .eq. 0>;
~1 mN+~NB+ <bu -1010>;
~2103 <bi -2000, c67 .eq. 0>;
~1 mN+~NC+ <bu -1010>;
~2104 <bi -2000, c68 .eq. 0>;
~1 mN+~ND+ <bu -1010>;
~2105 <bi -2000, c69 .eq. 0>;
~1 mN+~NE+ <bu -1010>;
~2106 <bi -2000, c70 .eq. 0>;
~1 mN+~NF+ <bu -1010>;
~2107 <bi -2000, c71 .eq. 0>;
~1 mN+~NG+ <bu -1010>;
~2108 <bi -2000, c72 .eq. 0>;
~1 mN+~NH+ <bu -1010>;
~2109 <bi -2000, c73 .eq. 0>;
~1 mN+~NI+ <bu -1010>;
~2110 <bi -2000, c74 .eq. 0>;
~1 mN+~NJ+ <bu -1010>;
~2111 <bi -2000, c75 .eq. 0>;
~1 mN+~NK+ <bu -1010>;
~2112 <bi -2000, c76 .eq. 0>;
~1 mN+~NL+ <bu -1010>;
~2113 <bi -2000, c77 .eq. 0>;
~1 mN+~NM+ <bu -1010>;
~2114 <bi -2000, c78 .eq. 0>;
~1 mN+~NN+ <bu -1010>;
~2115 <bi -2000, c79 .eq. 0>;
~1 mN+~NO+ <bu -1010>;
~2116 <bi -2000, c80 .eq. 0>;
~1 mN+~NP+ <bu -1010>;
~2117 <bi -2000, c81 .eq. 0>;
~1 mN+~NQ+ <bu -1010>;
~2118 <bi -2000, c82 .eq. 0>;
~1 mN+~NR+ <bu -1010>;
~2119 <bi -2000, c83 .eq. 0>;
~1 mN+~NS+ <bu -1010>;
~2120 <bi -2000, c84 .eq. 0>;
~1 mN+~NT+ <bu -1010>;
~2121 <bi -2000, c85 .eq. 0>;
~1 mN+~NU+ <bu -1010>;
~2122 <bi -2000, c86 .eq. 0>;
~1 mN+~NV+ <bu -1010>;
~2123 <bi -2000, c87 .eq. 0>;
~1 mN+~NW+ <bu -1010>;
~2124 <bi -2000, c88 .eq. 0>;
~1 mN+~NX+ <bu -1010>;
~2125 <bi -2000, c89 .eq. 0>;
~1 mN+~NY+ <bu -1010>;
~2126 <bi -2000, c90 .eq. 0>;
~1 mN+~NZ+ <bu -1010>;
~2199 <return>;
!stop DMDX from exiting;

Macro comparison example

    Perhaps more useful, you can use the stack operators to compare two macros if you say wanted to compare subject input (using a macro input routine above or the <prose> keyword) to a specifc value:

<ep>
<fd 30> <vm desktop> <id keyboard>
<!branchdiagnostics>
<eop>

0 "macro comparison demo";
~1 <md .strA. targeter> <md .strB. target> <call 1111>;
~1 <bi 20, c1111 .ne. 0>;
10 "strings not the same" <bu 30>;
20 "strings the same";
~30 <md .strA. target> <md .strB. target> <call 1111>;
~1 <bi 20, c1111 .ne. 0>;
10 "strings not the same" <bu 30>;
20 "strings the same";
30 "done" L;

~1111 <set c1112 = 0> <set c1113 = 0>;
~1120 <macro pop .strA., 1112> <macro pop .strB., 1113> <bi 1130, c1112 .ne. c1113>;
~1124 <bi -1120, c1112 .ne. 0> <! if end of one string must be end of both as the values are the same so must be success otherwise do next char>;
~1126 <set c1111 = 1> <return>;
~1130 <set c1111 = 0> <return>;

! comment to stop dmdx auto exiting when previous item executes;


Macro Set Operation  Keyword

<macro set name = expression>

   Rounding out my wish list of features since forever is the set operator added in version 4.3.0.0.  This is really handy if there's some keyword that you want a parameter set from an expression and DMDX doesn't allow it otherwise however it still requires a little care in using it due to the fact (mentioned above) about macros being expanded at item read time and only defined later as the item is parsed.  So the following for instance does not result in counter 49 getting assigned "49" and instead if macro .body. hadn't been defined before would get flagged as a syntax error:

~1 <macro set .body. = 7*7> <set c49 = ~.body.>;

   Instead the correct solution is to use two items (note they're skip display items so they execute almost as fast as one does):

~1 <macro set .body. = 7*7>;
~1
<set c49 = ~.body.>;



DMDX Index.