;PUPPRP.MAC;13 26-SEP-82 15:14:34 EDIT BY TAFT ; Add Desired-Property property ; Directory property parser strips extraneous surrounding < > ;PUPPRP.MAC;12 7-NOV-81 14:03:25 EDIT BY TAFT ; Fix bug in parsing of special version properties. ;PUPPRP.MAC;11 24-APR-81 21:08:17 EDIT BY TAFT ; Add code to date property parsers to permit time zone preceded ; by space rather than hyphen (stupid Tenex restriction). ;PUPPRP.MAC;10 29-AUG-80 15:48:10 EDIT BY TAFT ; Permit special version property values *, H, L, N. ;PUPPRP.MAC;8 20-JAN-80 17:42:13 EDIT BY TAFT ; Permit optional ".registry" appended to User-name property ;PUPPRP.MAC;7 2-SEP-79 16:00:50 EDIT BY TAFT ;PUPPRP.MAC;6 2-JUN-77 21:32:49 EDIT BY TAFT ; Add "Author" and "Size" property parsers ;PUPPRP.MAC;5 31-MAR-77 20:25:23 EDIT BY TAFT ; Put in hooks for mail server ; Make individual property interpreters conditionally external ; Reinstate forcing device and directory to upper-case. ; (I knew there was a reason. Damn silly Tenex problem!) ;PUPPRP.MAC;3 19-MAR-77 20:05:26 EDIT BY TAFT ; Add code to parse date properties ;PUPPRP.MAC;2 18-MAR-77 18:14:45 EDIT BY TAFT ; Call external REFILL procedure in SCNPRP ;PUPPRP.MAC;1 18-MAR-77 17:06:55 EDIT BY TAFT ; Modified to be usable in both server and user ; Renamed PUPPRP.MAC ; Uniformly generate "No" response before giving error return. ; Remove device/directory name check -- it will be caught later if ; actually used. ;PFUPRP.MAC;3 15-MAR-77 18:54:26 EDIT BY TAFT ; Add Tenex-paged type ;PFUPRP.MAC;2 10-MAR-77 14:21:00 EDIT BY TAFT ; Split out from PUPFTP.MAC ; Copyright 1979, 1980, 1981 by Xerox Corporation TITLE PUPPRP -- PUP FTP PROPERTY LIST PARSER SUBTTL E. A. Taft / March 1977 SEARCH PUPDEF,STENEX ; Scan and interpret property list ; A/ Source string pointer (first char expected to be "(" ) ; B/ Pointer to property list storage region ; Returns +1: Syntax error, "No" reply already generated ; (caller may have to supply terminating EOC) ; +2: Successful, A/ Pointer to matching ")" ; The external REFILL procedure is called often. It should ; do any necessary management of the source string pointer in A. ; Clobbers C, D SCNPRP::PUSHJ P,SAVE1## ; Protect P1 MOVE P1,B ; Put plist pointer in protected ac TLC A,-1 ; Convert -1 lh string ptr TLCN A,-1 ; to standard byte ptr HRLI A,(POINT 7) ILDB B,A ; Check first char CAIE B,"(" ; Good start of property list? FTPM(NO,10,,1) ; Here to begin scanning new property SCNPR1: PUSHJ P,REFILL## ; Refill buffer if necessary ILDB C,A ; Get next char CAIN C," " ; Permit extra spaces here JRST SCNPR1 CAIN C,")" ; End of property list? JRST SCNPR9 ; Yes CAIE C,"(" ; Start of new property? FTPM(NO,10,,1) MOVE B,[-NPROPS,,PLDISP] ; Make ptr to property name table PUSHJ P,FNDKEY ; Get and lookup property name FTPM(NO,10,,1) JRST [ HRROI B,TEMP## ; Unrecognized property, say so DTYPE MOVEI C,5000 ; Max # characters to discard PUSHJ P,GTPVAL ; Scan and discard property value FTPM(NO,10,,1) JRST SCNPR3] ; Ignore property, on to next LDB C,A ; Check terminator CAIE C," " ; Space? FTPM(NO,10,,1) MOVE D,0(B) ; Found, get dispatch HLLM D,0(P) ; Save entry pointer in case error TRZ F,RAISEF ; Default is not to raise lower case PUSHJ P,0(D) ; Scan and store property value POPJ P, ; Failed, return +1 SCNPR3: LDB C,A ; Get terminator CAIE C,")" ; Proper end of property? FTPM(NO,10,,1) JRST SCNPR1 ; Yes, on to next property ; Here when done entire property list SCNPR9: SKIPN P.DPRP(P1) ; Any Desired-Property properties encountered? SETOM P.DPRP(P1) ; No, request all JRST SKPRET## ; Return +2 ; Construct property name and dispatch table DEFINE X(SYM,NAME,SIZE<1>) < IF2,> [ASCIZ /NAME/] ,, PP'SYM > PLDISP: PNAMES NPROPS==.-PLDISP ; Number of properties ; Subroutines to process individual properties ; All have the following calling sequence: ; A/ Source string ptr ; P1/ Property list pointer ; Returns +1: Error, reply already generated ; +2: Successful, A/ byte ptr to property value terminator ; May clobber B-D ; (Author ) PPAUTH: HRROI B,P.AUTH(P1) ; Where to put string MOVEI C,USRSTL ; Max # of characters PUSHJ P,GTPVAL ; Collect and store string FTPM(NO,10,,1) JRST SKPRET## ; Return +2 ; (Byte-Size ) PPBYTE: MOVEI C,^D10 ; Decimal radix NIN ; Convert number FTPM(NO,16,,1) CAIL B,1 ; Check for reasonable value CAILE B,^D36 FTPM(NO,16,,1) MOVEM B,P.BYTE(P1) ; Ok, store it in property list JRST SKPRET## ; Return +2 ; (Connect-Name ) - only server should receive this PPCNAM: PUSHJ P,GSTDIR ; Collect string and do STDIR FTPM(NO,10,,1) FTPM(NO,23,,1) HRRZM C,P.CNAM(P1) ; Store dir # in property list JRST SKPRET## ; Return +2 ; (Connect-Password ) - only server should receive this PPCPSW: HRROI B,P.CPSW(P1) ; Where to put string MOVEI C,USRSTL ; Max # of characters TRO F,RAISEF ; Raise lower case letters PUSHJ P,GTPVAL ; Collect and store password string FTPM(NO,10,,1) JRST SKPRET## ; Return +2 ; (Creation-Date ) PPCDAT: PUSHJ P,GETDAT ; Input date and time FTPM(NO,25,,1) MOVEM B,P.CDAT(P1) ; Ok, store it in property list JRST SKPRET## ; Property value processing routines (cont'd) ; (Desired-Property ) PPDPRP: MOVE B,[-NPROPS,,PLDISP] ; Make ptr to property name table PUSHJ P,FNDKEY ; Get and lookup property name FTPM(NO,10,,1) JRST [ HRROI B,TEMP## ; Unrecognized property, say so if debugging DTYPE JRST SKPRET##] ; Just ignore SUBI B,PLDISP ; Index of property name MOVNI B,0(B) MOVSI C,(1B0) ; Set desired-property bit LSH C,0(B) IORM C,P.DPRP(P1) JRST SKPRET## ; (Device ) PPDEVI: TRO F,RAISEF ; Raise lower case letters HRROI B,P.DEVI(P1) ; Where to put string MOVEI C,USRSTL ; Max length PUSHJ P,GTPVAL ; Collect and store device name FTPM(NO,10,,1) JRST SKPRET## ; Return +2 ; (Directory ) PPDIRE: TRO F,RAISEF ; Raise lower case letters HRROI B,P.DIRE(P1) ; Put property value here MOVEI C,USRSTL ; Max # characters PUSHJ P,GTPVAL ; Get property value string FTPM(NO,10,,1) ; Strip extraneous < > off directory name, since this is a common user error. MOVEI D,P.DIRE(P1) HRLI D,(POINT 7) ILDB C,D ; If string empty then just return JUMPE C,SKPRET## ADD B,[7B5] LDB C,B ; Last real char of string CAIE C,">" ; If it is ">" then smash it with null JRST .+3 SETZ C, DPB C,B LDB C,D ; First char of string CAIE C,"<" JRST SKPRET## MOVEI B,P.DIRE(P1) ; Strip "<" by sliding string down one char HRLI B,(POINT 7) ILDB C,D IDPB C,B JUMPN C,.-2 JRST SKPRET## ; Return +2 ; (End-Of-Line-Convention CR|CRLF|Transparent) PPEOLC: MOVE B,[-3,,EOLTAB] ; Set pointer to keyword table PUSHJ P,FNDKEY ; Get and lookup keyword FTPM(NO,17,,1) FTPM(NO,17,,1) HRRZ B,0(B) ; Succeeded, get entry value MOVEM B,P.EOLC(P1) ; Store in property list JRST SKPRET## ; Return +2 EOLTAB: [ASCIZ /CR/] ,, 0 [ASCIZ /CRLF/] ,, 1 [ASCIZ /TRANSPARENT/] ,, 2 ; Property value processing routines (cont'd) ; (Name-Body ) PPNAMB: MOVE B,[POINT 7,P.NAMB(P1)] ; Init byte ptr MOVEI C,NAMSTL ; Max length TRZ F,RAISEF ; Use this as period seen flag PPNAM1: ILDB D,A ; Get char from property value JUMPE D,[FTPM(NO,10,,1)] CAIN D,PQUOTE ; Character quote? JRST [ ILDB D,A ; Yes, get next literally JUMPE D,[FTPM(NO,10,,1)] JRST .+3] CAIN D,")" ; End of property value? JRST PPNAM4 ; Yes SOJL C,[FTPM(NO,13,,1)] ; Check length CAIN D,"." ; Period? JRST [ TRON F,RAISEF ; Yes, seen one already? JRST PPNAM3 ; No, store literally JRST PPNAM2] ; Yes, quote it PUSH P,D+1 ; Get another ac IDIVI D,^D36 ; Compute index into bit table MOVE D,FILQUO(D) ; See if need to quote character LSH D,(D+1) ; Set sign if so POP P,D+1 JUMPGE D,PPNAM3 ; Jump if not PPNAM2: MOVEI D,"V"-100 ; Insert a control-V IDPB D,B SOJL C,[FTPM(NO,13,,1)] ; Check length PPNAM3: LDB D,A ; Recover character IDPB D,B ; Store it JRST PPNAM1 ; Back for more PPNAM4: SETZ D, ; Done, append null IDPB D,B JRST SKPRET## ; Return +2 ; Bit table of characters that must be quoted with ^V for GTJFN FILQUO: 777777777770 ; 000-043 001200035600 ; 044-107 000000014000 ; 110-153 000007600000 ; 154-177 ; Property value processing routines (cont'd) ; (Read-Date ) PPRDAT: PUSHJ P,GETDAT ; Input date and time FTPM(NO,27,,1) MOVEM B,P.RDAT(P1) ; Ok, store it in property list JRST SKPRET## ; (Server-Filename ) PPSFIL: HRROI B,P.SFIL(P1) ; Where to put name string MOVEI C,SFNSTL ; Maximum length PUSHJ P,GTPVAL ; Get property value string FTPM(NO,10,,1) JRST SKPRET## ; Succeeded, return +2 ; (Size ) PPSIZE: MOVEI C,^D10 ; Decimal radix NIN ; Convert number FTPM(NO,10,,1) MOVEM B,P.SIZE(P1) ; Store in property list JRST SKPRET## ; Return +2 ; (Type Text|Binary|Tenex-Paged) PPTYPE: MOVE B,[-3,,TYPTAB] ; Set pointer to keyword table PUSHJ P,FNDKEY ; Get and lookup keyword FTPM(NO,15,,1) FTPM(NO,15,,1) HRRZ B,0(B) ; Succeeded, get entry value MOVEM B,P.TYPE(P1) ; Store in property list JRST SKPRET## ; Return +2 TYPTAB: [ASCIZ /BINARY/] ,, 2 [ASCIZ /TENEX-PAGED/] ,, 3 [ASCIZ /TEXT/] ,, 1 ; (User-Name ) - only server should receive this PPUNAM: PUSHJ P,GSTDIR ; Collect string and do STDIR FTPM(NO,10,,1) FTPM(NO,20,,1) SKIPGE C ; Make sure not files-only FTPM(NO,20,,1) HRRZM C,P.UNAM(P1) ; Store dir # in property list JRST SKPRET## ; Return +2 ; (User-Password ) - only server should receive this PPUPSW: HRROI B,P.UPSW(P1) ; Where to put string MOVEI C,USRSTL ; Max # of characters TRO F,RAISEF ; Raise lower case letters PUSHJ P,GTPVAL ; Collect and store password string FTPM(NO,10,,1) JRST SKPRET## ; Return +2 ; Property value processing routines (cont'd) ; (User-Account ) - only server should receive this PPUACT: HRROI B,P.UACT+1(P1) ; Where to put string MOVEI C,USRSTL ; Max # of characters TRO F,RAISEF ; Raise lower-case letters PUSHJ P,GTPVAL ; Collect and store account string FTPM(NO,10,,1) MOVE D,A ; Preserve source string ptr HRROI A,P.UACT+1(P1) ; See if account is numeric MOVEI C,^D10 NIN JRST PPUAC1 ; No, assume string TLNE B,(7B2) ; Yes, make sure in range FTPM(NO,22,,1) TLOA B,(5B2) ; Flag numeric account PPUAC1: HRROI B,P.UACT+1(P1) ; Here if string account MOVEM B,P.UACT(P1) ; Store account designator MOVE A,D ; Restore source string ptr JRST SKPRET## ; Return +2 ; (Version ) PPVERS: ILDB C,A ; First, see if special version *, H, L, N SETO B, CAIN C,"*" MOVEI B,-3 ; All versions ANDCMI C,40 CAIN C,"H" MOVEI B,0 ; Highest existing version CAIN C,"L" MOVEI B,-2 ; Lowest existing version CAIN C,"N" MOVEI B,-1 ; Next higher version JUMPGE B,[IBP A ; Jump if ok special version JRST PPVER1] ADD A,[7B5] ; Not special, back up pointer and try number MOVEI C,^D10 ; Decimal radix NIN ; Convert number FTPM(NO,14,,1) CAML B,[-2] ; Check for reasonable value CAILE B,777774 FTPM(NO,14,,1) PPVER1: MOVEM B,P.VERS(P1) ; Ok, store it in property list JRST SKPRET## ; Return +2 ; (Write-Date ) PPWDAT: PUSHJ P,GETDAT ; Input date and time FTPM(NO,26,,1) MOVEM B,P.WDAT(P1) ; Ok, store it in property list JRST SKPRET## ; Collect property value and do STDIR on it ; A/ Source string ptr ; Returns +1: Illegal format property value ; +2: STDIR failed ; +3: A/ Updated pointer, C/ flags,,dir# from STDIR ; Clobbers B, C ; Note: ignores ".registry" appearing at the end of the name, if any GSTDIR: HRROI B,TEMP ; Buffer property value here MOVEI C,USRSTL ; Max # characters PUSHJ P,GTPVAL ; Get property value string POPJ P, ; Failed, return +1 MOVE C,A ; Ok, save source string ptr MOVE A,[POINT 7,TEMP] ; Look for "." GSTDI1: ILDB B,A JUMPE B,GSTDI2 CAIE B,"." JRST GSTDI1 SETZ B, ; Found one, smash with null DPB B,A GSTDI2: SETZ A, ; Exact match required HRROI B,TEMP## ; Where the name string is STDIR ; Look up directory JRST SKPRET## ; Not found, return +2 JRST SKPRET## ; Ambiguous, return +2 EXCH C,A ; Ok, result to C, string ptr to A JRST SK2RET## ; Return +2 ; Get property value string (up to ")" ) ; A/ Source string ptr ; B/ Destination string ptr ; C/ Max # of characters permitted ; Returns +1: Error, overflowed or no terminating ")" ; +2: Successful, A/ byte ptr to terminating ")", ; B/ byte ptr to terminating null in destination. ; Terminates destination string with null. ; Converts lower case to upper if RAISEF is set ; Clobbers B-D GTPVAL::TLC B,-1 ; If lh is -1, convert to byte ptr TLCN B,-1 HRLI B,(POINT 7) GTPVA1: ILDB D,A ; Get char from source JUMPE D,CPOPJ## ; Fail if end of source string CAIN D,PQUOTE ; Character quote? JRST [ ILDB D,A ; Yes, get next literally JUMPE D,CPOPJ## ; But don't allow null JRST GTPVA2] CAIN D,")" ; End of property value? SETZ D, ; Yes, remember so CAIL D,"a" ; Lower case? CAILE D,"z" JRST GTPVA2 ; No TRNE F,RAISEF ; Yes, want to raise it? SUBI D,40 ; Yes, do so GTPVA2: SOJL C,CPOPJ## ; Fail if overflowing destination IDPB D,B ; Store byte in destination JUMPN D,GTPVA1 ; Repeat if not end of property JRST SKPRET## ; Done, return +2 ; Get date property ; A/ Source string ptr ; Returns +1: Error ; +2: Successful, A/ byte ptr to terminating ")", ; B/ date/time word in internal format ; Clobbers B-D GETDAT: SETZ B, ; Arbitrary input format IDTNC ; Input date without conversion to internal POPJ P, ; Illegal date format PUSH P,B ; Save results PUSH P,C LDB B,A ; More to come? CAIE B," " JRST GETDA9 ; No, just convert what IDTNC gave us ; Accept time zone of the form {A|E|C|M|P|Y|H}{S|D}T | GMT | {+|-}hh[:mm] HRLI D,(1B0+1B2) ; Use DST as specified by B1; use zone given ILDB B,A ; Char after space CAIE B,"+" CAIN B,"-" JRST GETDA4 ; Numeric time zone ANDI B,137 ; Convert char to upper-case CAIN B,"G" JRST GETDA7 ; Probably GMT MOVSI C,-7 ; Try all American time zones CAME B,[EXP "A","E","C","M","P","Y","H"](C) AOBJN C,.-1 JUMPGE C,GETDAE ; Jump if no match TLO D,4(C) ; Matched, set zone (Atlantic = 4) ILDB B,A ; Next char ANDI B,137 CAIN B,"S" JRST GETDA2 ; Standard, no adjustment CAIE B,"D" JRST GETDAE ; Failed TLO D,(1B1) ; Use daylight savings time GETDA2: ILDB B,A ANDI B,137 CAIN B,"T" JRST GETDA8 ; Ended with "T", successful GETDAE: SUB P,[2,,2] ; Failed, return +1 POPJ P, ; Here for numeric zone. GETDA4: ADD A,[7B5] ; Back up string ptr to include sign MOVEI C,^D10 NIN JRST GETDAE ANDI B,77 ; Sign-extended 6-bit zone TLO D,0(B) ; Insert zone in control word LDB B,A ; Check terminator CAIE B,":" JRST GETDA8 ; No more GETDA5: ILDB B,A ; More to come, skip over digits CAIL B,"0" CAILE B,"9" JRST GETDA8 JRST GETDA5 ; Here for possible GMT GETDA7: ILDB B,A ANDI B,137 CAIN B,"M" JRST GETDA2 ; Ok, now look for "T" JRST GETDAE GETDA8: IBP A ; Point to terminator GETDA9: POP P,C POP P,B IDCNV ; Convert to internal POPJ P, ; Failed ?? JRST SKPRET## ; Successful ; Get and lookup name keyword string ; A/ Source string ptr ; B/ -length ,, address of lookup table (see NAMSRC) ; Returns +1: Error, improper format ; +2: Name not found ; +3: Successful, B/ pointer to matching entry ; Clobbers B-D; updates A appropriately on +2 and +3 returns FNDKEY::PUSH P,B ; Save table pointer HRROI B,TEMP## ; Where to buffer string PUSHJ P,GETNAM ; Input the name JRST [ POP P,B ; Failed, return +1 POPJ P,] EXCH A,0(P) ; Ok, save string, get table HRROI B,TEMP## ; Where the name is now PUSHJ P,NAMSRC ; Look it up JRST [ POP P,A ; Not found, recover string ptr JRST SKPRET##] ; Return +2 MOVE B,A ; Ok, copy entry pointer POP P,A ; Recover string ptr JRST SK2RET## ; Return +3 ; Get name keyword string ; A/ Source string ptr ; B/ String ptr to temp region in which to store name ; Ignores leading blanks. Converts lower to upper case. ; Terminates on any character besides alphanumeric and "-". ; Terminates temp string with null (for NAMSRC). ; Returns +1: Error, first char not keyword constituent ; +2: Ok, A/ Byte ptr to terminator ; Clobbers B-D GETNAM::TLC B,-1 ; If lh is -1, convert to byte ptr TLCN B,-1 HRLI B,(POINT 7) SETZ D, ; Init counter GETNA1: ILDB C,A ; Get char from source CAIL C,"A" ; Alphabetic? CAILE C,"Z" CAIN C,"-" ; Hyphen? JRST GETNA2 ; Yes, append to string CAIL C,"0" ; Numeric? CAILE C,"9" CAIA JRST GETNA2 ; Yes, append to string CAIL C,"a" ; Lower case? CAILE C,"z" JRST GETNA3 ; No, terminator SUBI C,40 ; Yes, capitalize GETNA2: IDPB C,B ; Store in temp buffer AOJA D,GETNA1 ; Count and loop ; Here when hit terminator GETNA3: CAIN C," " ; Blank? JUMPE D,GETNA1 ; Yes, ignore if leading JUMPE D,CPOPJ## ; Fail if string empty SETZ C, ; Append null to temp string IDPB C,B JRST SKPRET## ; Return +2 ; Lookup name in table ; A/ -length ,, address of table to search ; B/ String ptr to name string (all letters must be capitals) ; Returns +1: Not found, A points to smallest entry > key ; +2: Found, A points to matching entry ; In both cases, A is still in AOBJN pointer format. In the ; +1 return, the lh is positive if A points past end of table. ; Table entry format: ; [ASCIZ /NAME/] ,, value ; Clobbers A-D NAMSRC::TLC B,-1 ; If lh is -1, convert to byte ptr TLCN B,-1 HRLI B,(POINT 7) JSP C,BINSRC ; Call binary search ; Name comparison routine for binary search ; A/ Address of table entry to compare in rh ; B/ Search key (as passed to BINSRC) ; Returns +1: Key < Entry ; +2: Key > Entry ; +3: Key = Entry ; Additionally, if the key is an initial substring of the entry ; (+1 return only), returns D/ string ptr to tail (else 0) ; Clobbers C, D NAMCMP::PUSH P,A ; Save args PUSH P,B HLRZ A,0(A) ; Make string ptr to table entry HRLI A,(POINT 7) NAMCM1: ILDB C,A ; Get char from table entry ILDB D,B ; Get char from search key CAIGE D,(C) ; Compare JRST [ JUMPN D,NAMCM3 ; Key < entry; if not end return +1 MOVSI D,(7B5) ; If end of key make string ptr ADD D,A ; to tail of entry JRST NAMCM4] ; Also return +1 CAILE D,(C) JRST NAMCM2 ; Key > entry, return +2 JUMPN D,NAMCM1 ; Key char = entry, look at next AOS -2(P) ; End, key = entry, return +3 NAMCM2: AOS -2(P) NAMCM3: SETZ D, ; Note not substring match NAMCM4: POP P,B ; Restore args POP P,A POPJ P, ; Perform binary search ; A/ -length ,, address of table to search ; B/ Search key ; C/ Routine to call to compare key to entry ; Returns +1: Not found, A points to smallest entry > key ; +2: Found, A points to matching entry ; In both cases, A is still in AOBJN pointer format. In the ; +1 return, the lh is positive if A points past end of table. ; Clobbers A-D ; The comparison routine must operate as follows: ; A/ Address of table entry to compare in rh ; B/ Search key (as passed to BINSRC) ; Returns +1: Key < Entry ; +2: Key > Entry ; +3: Key = Entry ; C and D may be clobbered freely, others must be protected BINSRC::PUSHJ P,SAVE2## ; Need more temps MOVE P2,C ; Save routine to call HLRE C,A ; Get negative table length MOVN C,C ; Make positive JFFO C,.+2 ; Find position of first 1 POPJ P, ; Empty table, fail MOVN D,D ; Compute largest power of 2 MOVSI P1,(1B0) ; <= table length LSH P1,(D) HRLI P1,(P1) ; Put in both halves SUB A,[1,,1] ; Backup ptr to one before table BINSR1: ADD A,P1 ; Add increment to table pointer BINSR2: LSH P1,-1 ; Halve increment (both halves) TRZ P1,400000 JUMPGE A,BINSRL ; Jump if off end of table PUSHJ P,0(P2) ; Call routine to do compare JRST BINSRL ; Key < entry JRST BINSRG ; Key > entry JRST SKPRET## ; Key = entry, return +2 ; Here if key > entry: advance table pointer BINSRG: JUMPN P1,BINSR1 ; Loop if increment nonzero AOBJN A,CPOPJ## ; Set pointer and fail if zero ; Here if key < entry, or past end: backup table pointer BINSRL: JUMPE P1,CPOPJ## ; Fail if increment zero SUB A,P1 ; Backup table pointer JRST BINSR2 ; Try again END