KBD-PM.TXT R.J.Holmgren 29 May 90 [XYWPMKIT.ZIP] The common wisdom is, that you can't establish or manipulate a Save/Get from the KeyBoarD file - that you can't do XPL programming in .KBD! In fact, you can. Here's how. Step 1: Write a Save/Get in the KBD file ----------------------------------------- Try this (here, loaded on key F11 -- copy right out of this file, and change the scan code and file name as required; pick a file that exists in your current directory, for test purposes -- the example is SHORT.HLP): 87=BC,r,u,n, ,t,e,s,t,.,p,m, ,AE,S,V,0,1,CO,s,h,o,r,t,.,h,l,p,AF,XC Note that the guillemets (Ascii-174 and 175, "AE" and "AF") are three-byte codes -- the same codes usually placed in your keyboard when you directly input these two "hot" characters from the keyboard. One-byte guillemets will beep at you if you view your KBD file in Normal editing mode. STore and LOAD (or LDKBD) your KeyBoarD file. The "CO" is function , which is used expressly to insert a COmma in the KeyBoarD file. Step 2: Interpret KeyBoarDed Save/Get within a PrograM ------------------------------------------------------- Here's a small TEST.PM. Hard-code the name of *another* file that also exists in your current directory in the *initial* statement of AESV01AF in TEST.PM (example, LONG.HLP): LB TEST.PMSV01,long.hlpSV02,SV01,IFIS02@UPR(IS00)>-1PV00EIca PV01 EX Now STore TEST.PM as a file, and RUN it directly from the CoMmand line: run test.pm LONG.HLP is CAlled into an available window. Goodie. ABort it. Now hit F11 (or whatever key you've put our little Step 1 keyboard assignment on). No good! XyWrite misleadingly responds "Extra start command" and proceeds to load LONG.HLP, which is the filespec hard-coded in AESV01AF in TEST.PM. In fact, there's no Extra start command. What's the problem? The problem lies in the evaluation by AESV00AF of the CM line. You have passed an argument -- AESV01,short.hlpAF -- to program TEST.PM by means of the space (or comma or whatever) in the KeyBoarded command. Arguments to PMs are stored in Save/Get 00. I don't know exactly what happens next, because its an internal XyWrite operation, and XyQuest is especially close-mouthed about what's going on inside EDITOR (it would, for example, be helpful if they'd publish the source for the XPL executive, so programmers can figure out how to manipulate XPL -- no danger whatsoever that anybody would ever clone this ridiculously labored language!). But if concatenation is any guide, I'd guess that there are three terminal guillemets (Ascii-175s) at the conclusion of AESV00AF, which is crashing the Save/Get and hence the whole routine. Probably, arguments are automatically stored within AESV00AF by EDITOR as follows: AESX00,AEargumentAFAF (which is the same as AESX00,AEASAFAF). But because our argument itself terminates in Ascii-175, we have: AESV00,AEAESV01,short.hlpAFAFAF. Three guillemets in a row *always* crash. We require either AESV00,AEAESV01,short.hlpAFAFAF or AESV00,AEAESV01,short.hlpAFAFAF. (People are always asking me why I sprinkle my PMs with so many func s. It's because my PMs load Save/Gets within Save/Gets within Save/Gets within Save/Gets. prevents 175 pile-up, and allows embedding to many levels.) KBD Solution: ------------ Insert a function NO (No-Op) in the string. But the method is neither obvious nor easy! This KBD command doesn't work: 87=BC,r,u,n, ,t,e,s,t,.,p,m, ,AE,S,V,0,1,CO,s,h,o,r,t,.,h,l,p,AF,NO,XC because it just transparently *executes* an NO on the CMline, where NO performs "no operation". Charming. Whereas we want func NO to be a string literal, and *part* of the argument! The answer is, write NO as a five-byte code in the KBD -- {three-byte Ascii-255}{Ascii-129}{Ascii-163} which *looks like* "FF"FF-- whence it becomes, by virtue of keyboard-handling rules, a three-byte string literal on the CMline (copy the code right out of this file into your KBD file): 87=BC,r,u,n, ,t,e,s,t,.,p,m, ,AE,S,V,0,1,CO,s,h,o,r,t,.,h,l,p,AF,FF,,,XC Now run TEST.PM. It works, and CAlls SHORT.HLP. Direct CMline Command Solution: ------------------------------- If you try to put this on the CMline: run test.pm AESV01,short.hlpAF it fails because functions can't be placed on the CMline. But there are still ways to do it, albeit awkward. Easiest is a KBD assignment to put the NO string literal on the CMline, written in form "nn=NO,{three-byte Ascii-255},," -- thus: 87=NO,FF,, To implement this, you'd type "run test.pm AESV01,short.hlpAF" on the CMline, then hit F11 (key 87), and eXeCute. Even if you can't justify dedicating a key to this literal string, every power user ought to have Ascii-255 by itself on a key (so many and such interesting uses!): 87=FF To implement this, you'd type "run test.pm AESV01,short.hlpAF" on the CMline, hit F11, hit four (4) Backspaces [func +] in a row, enter from the keypad, and . The four Backspaces have the effect of entering something (anything, doesn't need to be a Backspace really) to satisfy XyWrite's need to bind Ascii-255 with two characters to its right, and then rubbing out those two characters to leave a one-byte Ascii-255 sitting there on the CMline all by itself -- then enter "" from the keypad. Simplest, and pure (requires no keyboard assignments at all), is this: MDRVCMMDNMrun test.pm AESV01,short.hlpAF++ IMPLICATIONS ------------ TEST.PM illustrates one obvious consequence: the revolutionary ability to replace hard-coded variables (Save/Gets) in PrograMs with new variables passed at runtime. To override hard code which isn't intended to be user-definable! If you're a programmer, and you think about that, it's a mind blower. A back door. Moreover -- very important -- Save/Gets are passed **without affecting passage of ordinary AESV00AF text arguments**! They still pass normally; Save/Get code hides within them, transparently. I've used this feature in late versions of OODLES (v2.0s+), to permit users to access secondary LIBraries in place of the main LIBrary that's hard-coded in OODLES.PM. This means, for example, that you could keep all your long routines in the main LIBrary, but put the little quickies that you want to load and eXeCute *fast* in a secondary LIBrary (routine "sys" in secondary LIBrary SHORT.LIB, loaded on SysReq key 84): 84=GH,EL,SI,s,y,s, ,AE,s,v,0,2,CO,e,:,\,s,h,o,r,t,.,l,i,b,AF,FF,,,@O Programming on the CoMmand Line: ------------------------------- There are many ways to use embedded .KBD file Save/Gets. Let's experiment. First, dedicate one ordinary Save/Get to a single command: AEPV00AF (no , no *nothing* before or after AEPV00AF). Load "AEPV00AF" from a file onto e.g. Save/Get A. Then go to the CMline and try this: MDRVCMMDNMAESX01,42+42AEPV01AFFF{hit Save/Get A} Voil! At far right, "84". Try it in the KBD file (88=F12): 88=BC,AE,S,X,0,1,CO,4,2,+,4,2,AF,AE,P,V,0,1,AF,FF,,,@A Shit! "84". So, is it possible to do programming on the CoMmand line? Yes. Can we write programs in the KBD and execute them on the CMline? Yes. Try concatenation: 88=BC,AE,S,V,0,1,CO,p,r,o,g,r,a,m,s, ,AF,AE,S,V,0,2,CO,N,i,f,t,y, ,AF,AE,S,X,0,3,CO,AE,I,S,0,2,AF,+,AE,I,S,0,1,AF,AF,AE,P,V,0,3,AF,FF,,,EL,@A Can the code, prior to execution, exceed 78 characters (the length of the CMline)? Yes and No. Each individual operation has to execute in 78 chars, but using func EL, you can put the result at the left of the command line, clear the rest of the line, reformat your results at left, and thus break the 78 char boundary to keep a KBD-PM going. The following does two concatenations of four $strings, and clears the CMline once: 88=BC,SI,AE,S,V,0,1,CO,p,r,o,g,r,a,m,AF,AE,S,V,0,2,CO,n,i,f,t,y, ,AF,AE,S,X,0,3,CO,AE,I,S,0,2,AF,+,AE,I,S,0,1,AF,AF,AE,P,V,0,3,AF,FF,,,EL,@A,RE,AF,EL,AE,S,V,0,1,CO,ER,AE,S,V,0,2,CO,A, ,AF,AE,S,V,0,4,CO,CO,20,e,h,?,AF,AE,S,X,0,3,CO,AE,I,S,0,2,AF,+,AE,I,S,0,1,AF,+,AE,I,S,0,4,AF,AF,AE,P,V,0,3,AF,FF,,,EL,@A,RE Note the rules: you must state your code logically, as you would in an ordinary PM, from left to right. And you must do it *on the CMline* (it's easy to forget the initial ). You only require the "FF" after the *last* Save/Get on the line, or where func would otherwise be required (to interrupt three consecutive Ascii-175s). You must use three-byte guillemets in the KBD file. After you've debugged your routine, func DX will make it run faster; put the DX immediately before or after the initial BC, then terminate with DO FF . Functions which are intended to operate only when the CMline is eXeCuted with AEPV00AF MUST be stated in 5-byte form. Let's do something useful. Here are two simple PMs from OODLES.LIB: Set_Mark and Get_Mark. They usurp Extended Save/Get 303, which is against my principles, but what the hell... No other way. Almost every other XPL programmer I see scatters Extended Save/Gets all over the place; I've used them exactly three times -- in the following two routines, and once in OODLES (where it isn't even necessary, as OODLES.DOC notes). Extended Save/Gets are not elegant. LBsetLB Put_Mark (Sets AESV303AF)SX303,CPPR DoneEX LBgetLB Find_Mark (Gets AESV303AF)ASU02,SX01,IS303PV02SX02,VA$ERIFPV02==19 No mark existsGLAEIjmp PV303 LBA}?;EX First, the "set" routine (87=key F11), then "get" (88=F12). The routines are rewritten below in small pieces, to fit on the CMline. The code is shown first as if it were a regular PM, then in KBD form. Functions contained within "{func}" braces are coded as five-byte sequences, so that they will perform only when the CMline is eXeCuted by (you'll find the underlying 5- byte characters for every function in v3.55- listed at routine AELBfuncAF in OODLES.LIB): "set"=87 rewritten for the KBD: AESX303,AECPAFAF{}}AEPR DoneAFAEEXAF{} 87=BC,AE,S,X,3,0,3,CO,AE,C,P,AF,AF,FF,,,FF,,},AE,P,R, ,D,o,n,e,AF,AE,E,X,AF,FF,,,@A "get"=88 rewritten for the KBD: AESU02,AESX01,AEIS303AFAF{}AFAEPV02AFAESX02,AEVA$ERAFAFAEPV02AF{}wAFwAESX02,yAEIFAEPV02AF<>19AF{}jmp AEPV303AF{ }AEPV02AFAF{w}AESX02,{y}AEEIAF{}{}AEIFAEPV02AF==19AF No mark existsAEEIAF{}?;}AEPR DoneAFAEEXAF{} 88=DX,BC,SI,AE,S,U,0,2,CO,AE,S,X,0,1,CO,AE,I,S,3,0,3,AF,AF,FF,,,AF,AE,P,V,0,2,AF,AE,S,X,0,2,CO,AE,V,A,$,E,R,AF,AF,AE,P,V,0,2,AF,FF,,,EL,@A,RE,AF,EL,AE,S,X,0,2,CO,ER,AE,I,F,AE,P,V,0,2,AF,<,>,1,9,AF,FF,,,j,m,p, ,AE,P,V,3,0,3,AF,FF,,09,FF,,,AE,P,V,0,2,AF,AF,FF,,,FF,,w,AE,S,X,0,2,CO,FF,,y,AE,E,I,AF,FF,,,@A,FF,,,AE,I,F,AE,P,V,0,2,AF,=,=,1,9,AF, ,N,o, ,m,a,r,k, ,e,x,i,s,t,s,AE,E,I,AF,FF,,},FF,,?,FF,,;,AE,P,R, ,D,o,n,e,AF,AE,E,X,AF,FF,,,@A All the functionality of programs, even conditionals and PRompts, are available from the KeyBoarD file. But the feasibility of these routines *depends* upon the existence of AEPV00AF in an ordinary or programming Save/Get that can be called from the KeyBoarD file (e.g. ). In practice, routine 87 is appropriate for the KBD, while 88 might better be put in an OODLES routine (it eats keyboard buffer memory). Here's a simple routine to Open a Line Before Current Paragraph, which fits nicely on two CMlines. (It may appear to contain unnecessary code, but the purpose is to trap a XyWrite "bug" which occurs when you write in Normal [formatted] text mode, the cursor is located in the first paragraph [there is no preceding Carriage Return], and the paragraph opens with a MoDe command.) Ordinary Save/Get A contains "AEPV00AF". "{func}" = 5-byte function $tring. Note that we issue 3-byte functions wherever possible, for .KBD file brevity and for speed. If you think about the statement "{}" in the top line, you'll realize how the rest of the code works. The first 3-byte real-time puts the cursor on the command line, where keyboard PMs must be executed; the second 5-byte {} is passively placed on the CMline, to be eXeCuted only when the entirety of this CMline code is written out, and (AEPV00AF) is issued to eXeCute it all at once. The underlying code: Aes 1 {}AESX01,-1AFseb 0A{ }AEIFAEERAFAFAESX01,AEVA$DTAFAFAEGLAAFAEEIAF0AAELBAAF{}AESX01,AEPV01AFAFAEIFAEPV01AF>0AF{}0A{7}AEEIAFAEIFAEPV01AF==0AF{}0A{7}AEEIAF{}es 1 }?; In the .KBD (key 24="O" for "O"pen): 24=SI,DX,BC,e,s, ,1,XC,BC,FF,,,AE,S,X,0,1,CO,-,1,AF,s,e,b, , ,0A,FF,,09,AE,I,F,AE,E,R,AF,AF,AE,S,X,0,1,CO,AE,V,A,$,D,T,AF,AF,AE,G,L,A,AF,AE,E,I,AF,0A,AE,L,B,A,AF,FF,,,AE,S,X,0,1,CO,AE,P,V,0,1,AF,AF,@A,AE,I,F,AE,P,V,0,1,AF,>,0,AF,FF,,,FF,,,0A,FF,,7,FF,,AF,AE,E,I,AF,AE,I,F,AE,P,V,0,1,AF,=,=,0,AF,FF,,,0A,FF,,7,AE,E,I,AF,FF,,,@A,e,s, ,0,XC,BC,GT,DO,FF A practical consideration is that keyboard PMs do not conserve memory. Each KBD-PM occupies about twice the space of a PM stored in an OODLES LIBrary (because of the commas -- although a discreet DOS-filed PM will require minimum 2K bytes, which is a waste). Still, keyboard PMs are fast, and self-contained, and above all, interesting. How come this kind of info never appears in _XyI_ "Tech Tips"?