NOTE:- Macros will require a little extra attention when the Unicode code path is on. Not only that in combination with the aforementioned Word idiocy (since obviated with DMDX 6.1.5.2) I recently saw a ~~ sequence with Word control words between the two and that upset the Unicode sequence so it was now out of phase with the byte order and it produced a string of mojibake. Pouring the item file through WordPad or retyping the ~~ solved the problem, as of 6.1.5.2 hopefully the problem has gone away for good.
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 CENTER> 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 quite some time ago 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 (it even allows limited backspacing) without using the <ZillionTypedResponses> code (at the time .zilliontext. hadn't been implemented). 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>;
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 (so this is an item file that you'd probably want to set
<macro yikwid> to suppress the warnings)::
~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>.
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 the designated counter
on
to that macro (so it becomes the leftmost character) or pop the contents of that
macro (left most character) into
the designated counter. Most of the time
the designated counter
will contain the ASCII decimal value of character being pushed onto the macro or
popped off
(so for instance A is 65 and Z is 90 although these days with version 6.1.0.1
having
character constants
in expressions you should only have to look up things like DMDX special
characters because you can have things like <set c.char. = 'A'>
instead of
<set c.char. = 65>). 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 (below) 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 (if it hadn't been
superseded with <macro set>
anyway).
The push and pop operations are similarly indispensible in the
tcpip reply parsing example in the
<send> and
<storecoords> keyword documentation.
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
Macro Stack Operation Keywords
<macro pop name,
designator>
<macro push
name,
designator>
counter designators:
counter.countername.
c.countername.
counterN
cN
N
Anagram task
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
specific 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 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>;
Macro Fixed Point Set Keyword
<macro fxpset N name = expression>
Back in the dark ages when I first added the expression evaluator to DMDX there was no call for floating point capability at all, however as DMDX has evolved there are now several cases where they're used, screen coordinates can now be between zero and one and both font and bitmap multipliers can be fractional as well and when using <macro set> to manipulate them one needs to prepend leading zeroes to the fractional component of the value as shown in the Stretch Blt modes discussion. This is a right pain and having recently come across another example that needs this in spades we now have the fxpset option. Here one decides how many fixed decimal places one is going to use and passes that as N and multiplies the values in the expression by a corresponding amount (so for three decimal places you multiply by 1000). So from my Corsi Block Tapping task where we have nine different targets on the screen and the coordinates of them have been generated randomly in counters 100 to 117 (a pair of them for each target, first being X and second being Y) with values between 0 and 100 where in order to display them they need to be 0.00 to 1.00 we have: ~1 <macro fxpset 2 .b1x. = c100> <macro fxpset 2 .b1y. = c101>
Note that we are deliberately using two items, one to set the macros and one to use them as per the usual you can't define a macro and use that new value in the same item.