Macro Stack Operation Keywords
<macro pop name,
designator>
<macro push
name,
designator>
macro name designators:
c (a single ASCII
char)
.macroname.
counter designators:
counter.countername.
c.countername.
counterN
cN
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 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 (the value 0, not the character '0' which is perfectly valid) 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
on the root macro page and 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.
Anagram task
The principal feature of the anagram task that sets it apart from all other typing code in DMDX is that it 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 in the macro page 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 button 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.
With the advent of DMDX 6.4.0.0's conversion of Unicode to UTF-8 outside quotes building a version of the anagram task that works with other alphabets becomes a possibility, but there are several problems. First off is finding the names of the keys on the keyboard for the 2100 series items and the <mwb> which means that any version of the task is still going to be tied to that locality. Second is that when popping and comparing characters one is going to have to be doing some bit twiddling as one UTF-8 character can extend for up to four bytes so one will have to first see if the upper bit (0x80) is set and then continue copying or comparing bytes while bit 8 is set and bit 7 (0x40) is not set. Similarly the backspacing logic would have to check if it was initially dumping a byte with the high bit set and if so keep on dumping till the two high bits are set as it's processing the UTF-8 backwards, ho hum...
<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;
DMDX Index.