
.BEGIN RFNL	;-*-MIDAS-*-

SUBTTL Routines for parsing and printing filenames

;Basic conventions:

;We assume that there are accumulators A, B, C, and D, not necessarily
;consecutive, and that the stack is in P.
;No routine clobbers ACs other than the
;ones it is documented to clobber, and none touches even temporarily
;any AC other than A, B, C, D and P.
;All code generated is pure, except for a few .SCALAR definitions.
;The main routines PFN, PFNCT and PFNMCH never skip.
;The main routines RFN and MERGE skip if the filename block to
;be filled in was big enough for the data to be put in it.
;The main routine STKMRG skips if it was able to allocate the result string.

;;; Filename blocks:

;A filename block consists any number of two-word entries,
;each of which holds a pair of byte pointers to the beginning and end
;of one component of a filename string.
;The last character in the component is one of ":", ";", " ", ^@, ^X or ^Y,
;and it identifies the type of component.

;;; Parsing and unparsing filenames:

;This file contains two routines, RFN to read filenames and PFN to print.
;The $$RFN and $$PFN assembly switches control them.
;Both expect a b.p. for ILDB'ing or IDPB'ing the text, in D.
;Both expect an AOBJN pointer to a filename block in B.

;For RFN, the AOBJN pointer in B should point to the space
;available for a filename block.  On return, B will be an AOBJN
;pointer to the space actually used.  RFN skips unless data
;was lost because the block was full.

;The filename block constructed by RFN contains pointers into the
;argument string, so that string must be kept intact until after the
;filename block is no longer needed.  In particular, it does not work
;to PFN the same filename block, or the result of merging it with another,
;into the same space that the original string was in.

;The RFN routine assumes that the user has defined the label RFNSPC
;which says which characters are terminators or start switches.
;RFNSPC should examine the character in A and skip if it should
;either terminate the filename or start a switch.
;If the character is "/" or "(" and $$SWITCH is 1, it will start
;a switch; otherwise, it will terminate the filespec.
;If RFNSPC does not skip, the character will neither terminate nor start a switch.
;However, RFNSPC is not called for the characters ":", ";", " ", and ^Q.
;For CR and ^@, it makes no difference whether RFNSPC skips.

;PFN similarly assumes that there is a routine PFNSPC which will
;skip for a character in A that needs a ^Q printed in front of it.
;Normally, RFNSPC and PFNSPC can be the same routine.

;If you want switches to be processed in filenames,
;set $$SWITCH to 1 and define the label SWITCH as a routine to read a switch.
;It will be called with the first character of the switch in A.
;It can read more characters off D, or by calling RFN"RUC
;If it skips, RFN assumes that the character in A should be reprocessed.
;A slash is followed by a single switch, while parentheses enclose
;any number of switches.  However, neither slash nor "(" will be
;recognized unless RFNSPC skips for it.  This gives the caller
;run-time control of whether switches are to be processed.

;If $$MNAME is set, the user must define MNAME to point to
;a word holding this machine's name in SIXBIT.

;;; Merging defaults:

;The MERGE routine takes two filename blocks (A and B) as input
;and a third AOBJN pointer in C to space to store a filename block as output.
;It copies the components of the first block, putting them in canonical order,
;and defaulting missing components from the second block.
;The results go in the third block.
;On return, C is an AOBJN pointer to the part of the output block
;which was actually filled with data,
;and MERGE skips unless data was lost because the block was full.

;Because the merged filename block contains pointers copied from the
;two input filename blocks, the strings which those input filename blocks
;were parsed from must be kept intact until the merged filename block
;is passed to PFN.

;^X and ^Y components in the first argument are replaced by the
;default names from the second argument to which they refer.

;The STKMRG routine is a high level interface which parses, merges
;and makes a string all on one.  It takes two ASCIZ strings,
;does RFN on each, does MERGE to the two filename blocks,
;then does PFN on the result, storing into a dynamically allocated string
;All temporary storage comes from the stack.

;These symbols should be defined by the user to select parts of this file:
IFNDEF $$RFN,$$RFN==0		;Include RFN, the routine for reading filenames.
IFNDEF $$SWITCH,$$SWITCH==0	;Include routines for processing "/" and "(-)" switches.
IFNDEF $$PFN,$$PFN==0		;Include PFN, the routine for printing filenames.
IFNDEF $$MNAME,$$MNAME==0	;1 => assume MNAME is defined and holds this machine's name.
IFNDEF $$MERGE,$$MERGE==0	;1 => provide MERGE routine to merge defaults.
IFNDEF $$STKMRG,$$STKMRG==0	;1 => provide STKMRG routine.

.AUXIL				;Don't mention all our internal symbols in crefs.

;PRINT VERSION NUMBER
.TYO6 .IFNM1
.TYO 40
.TYO6 .IFNM2
PRINTX/ INCLUDED IN THIS ASSEMBLY.
/

DEFINE SYSCAL NAME,ARGS
.CALL [SETZ ? SIXBIT/NAME/ ? ARGS ((SETZ))]
TERMIN

IFN $$RFN,[	;Routine for parsing a filename.

;Given a BP in D pointing to a filename,
;store in the filename block <- AOBJN ptr in B
;pointers to the beginnings and ends of the components of the filename.

;The block <- B contains two-word entries.
;In each entry is placed a pair of BPs, pointing to the start
;and end of one name (or device or directory) in the filename.

;Since B contains only pointers to the originally supplied string,
;you must not clobber the string itself until after you are through
;with the data in the filename block.

;Advances D through the string.
;Sets B to an AOBJN pointer to the used portion of the table;
;skips if the table was long enough.
;Clobbers C and A.

;If B's right half starts out as 0, then only the left half is incremented
;and the table is not stored.  So call with B/ SETZ to compute how long
;a table is required for a given string.
rfn:	push p,b
rfn0:	move c,d
rfn1:	ildb a,d
rfnunr:	caie a,";
	 cain a,":		;Check for special pathname syntax chars
	  jrst rfn2		;that always have these special meanings.
	cain a,40
	 jrst rfn2
	caie a,^X
	 cain a,^Y
	  jrst rfn3
	cain a,^Q
	 jrst [	ildb a,d
		jrst rfn4]
	pushj p,rfnspc		;Otherwise, ask user whether char is special.
	 jrst rfn4		;No skip => treat char as ordinary.
ifn $$switch,[
	cain a,"/		;If stopped on "/" or "(", call switch rtn.
	 jrst rfnsl		;Read 1 switch.
	cain a,"(
	 jrst rfnpar		;Read many switches until ).
];ifn $$switch
	setz a,			;If any unrecognized char is "special" according to the user,
	jrst rfn2		;it must be a terminator.

rfn4:	cain a,^M		;CR and null character terminate, even if quoted.
	 setz a,
	jumpn a,rfn1
rfn2:	push p,c		;Push this entry iff it contains more than one character.
	ibp c
	camn c,d
	 jrst [	pop p,c
		jrst rfn5]
	pop p,c
;Push this component, regardless of its length.
rfn3:	trnn b,-1
	 jrst [	add b,[2,,]	;But if B's rh is 0, just increment the lh; don't store.
		jrst rfn5]
	jumpge b,rfn6		;Don't actually store in table if past the end.
	movem c,(b)
	movem d,1(b)
rfn6:	add b,[2,,2]
rfn5:	jumpn a,rfn0		;If string not terminated, keep parsing.
	camge b,[2,,]		;Else skip if we did not run out of space in the table.
	 aos -1(p)
;Adjust B to contain a pointer to the used portion of the filename block.
	pop p,c			;Else pop original B into C.
	sub b,c
	trz b,-1		;LH(B) gets number of words pushed onto the filename block.
	movns b
	hrr b,c			;B gets AOBJN ptr to used portion.
	popj p,

IFN $$SWITCH,[	;Code for processing switches, when a "/" or "(" is seen.

rfnpar:	pushj p,ruc	;Get next char.  Is it a ")"?
rfnpa1:	cain a,")
	 jrst rfn1	;Paren ends switches; gobble next character.
	caie a,0
	cain a,^M
	 jrst rfnunr	;CR ends spec even in switch list; reread it.
	pushj p,switch	;Try to gobble the switch.
	 jrst rfnpar	;Char in A used up, get another.
	jrst rfnpa1	;Char in A not part of switch; is it ")"?

rfnsl:	pushj p,ruc
	caie a,0
	cain a,^M	;/<CR> ends spec; reread it.
	 jrst rfnunr
	pushj p,switch	;Otherwise, process it as switch.
	 jrst rfnunr	;No skip => char in A was gobbled by switch.
	jrst rfn1	;Skip => let next RSIXG gobble the char now in A.

;Read a char into A off the bp in D and convert to upper case.
ruc:	ildb a,d
	cail a,140
	 subi a,40
	popj p,

];IFN $$SWITCH

];IFN $$RFN

IFN $$PFN,[	;Routine to turn a filename block into a single string.

;Given a filename block of two-word entries <- AOBJN ptr in B,
;output a filename down the BP in D.
;Clobbers A and C.  Counts B out through the filename block.

;The filename components are printed in the order they appear.
;If you want them permuted into standard order,
;merge them with an empty block of defaults first with MERGE
;(since the output from MERGE always has the components in standard order).
pfn:	jumpge b,pfn2
pfnmc2:	jumpge b,pfn2a
	move c,(b)
	pushj p,pfn1
	add b,[2,,2]
	jumpl b,pfnmc2
pfn2a:	setz a,			;Replace the space at the end of the last
	dpb a,d			;entry with a null;
	ldb a,[300600,,d]	;then decrement the bp in D to point before the null.
	ldb c,[360600,,d]
	add c,a
	dpb c,[360600,,d]
	popj p,

pfn2:	setz a,			;If filename block is empty,
	move c,d		;store a null, but don't advance over it.
	idpb a,c
	popj p,

;Copy one entry's string into the output string.
pfn1:	camn c,1(b)		;Stop at end of entry.
	 jrst [	cain a,40	;If the entry ended in other than space, put in a space.
		 popj p,
		movei a,40
		idpb a,d
		popj p,]
	ildb a,c		;Fetch the next character.
	cain a,0		;If it's a terminating null,
	 movei a,40		;output a space instead.
	pushj p,pfnspc		;Is it special in this program?
	 jrst pfn3
	push p,a		;If so, put a ^Q in front of it.
	movei a,^Q
	idpb a,d
	pop p,a
pfn3:	idpb a,d		;In any case, output the char itself.
	caie a,^Q		;If it is a ^Q, don't check the following char
	 jrst pfn1		;for being special or for being ^Q.
	camn c,1(b)
	 popj p,
	ildb a,c
	idpb a,d
	movei a,1		;Don't leave space in A if it was quoted.
	jrst pfn1

;Return in D the number of characters required to hold a single string
;made from the parsed block of two-word entries pointed to by B.
;Clobbers A and C.

;Use this to decide how long a block to allocate for a string to be
;constructed with PFN.

;Note that the value returned is the length of the contents of the ASCIZ
;string that PFN will write.  It does not count the zero written
;to terminate that string.
pfnct:	setz d,
	jumpge b,cpopj
pfnct0:	jumpge b,[soja d,cpopj]
	move c,(b)
	pushj p,pfnct1
	add b,[2,,2]
	jrst pfnct0

pfnct1:	camn c,1(b)		;Stop at end of entry, but count one
	 jrst [	caie a,40	;for a following space if the entry didn't end in one.
		 cain a,0
		  aos d
		popj p,]
	ildb a,c		;Fetch the next character.
	addi d,1		;Count it.
	pushj p,pfnspc		;Is it special in this program?
	 jrst pfnct3
	addi d,1		;If so, count a ^Q for it.
pfnct3:	caie a,^Q		;If it is a ^Q, don't check the following char
	 jrst pfnct1		;for being special or for being ^Q.
	camn c,1(b)
	 popj p,
	ibp c			;Skip that char, but leave ^Q in A.
	aoja d,pfnct1		;DO count the char.

;Like PFN, but if the first component in the filename block is "DSK:"
;we output the machine name instead.
pfnmch:	pushj p,pfnmc1		;IS the first component "DSK:"?
	 jrst pfn		;No, just print normally.
	push p,b
IFN $$MNAME,move a,mname	;Yes, print the machine name instead
.ELSE [	syscal sstatu,[repeat 6,[? %clout,,a]]
	 .lose %lssys
]
	movei b,":		;with a colon after it
	pushj p,sixstr
	movei b,40		;and a space just as PFN would put after that.
	idpb b,d
	pop p,b
	add b,[2,,2]		;Discard the component "DSK:" and
	jrst pfnmc2		;process the remaining components.

;Skip if the first component in the filename block in B is "DSK:".
pfnmc1:	move c,(b)
irpc char,,[DSK:]
	ildb a,c
	caie a,"char
	 popj p,		;Return non-skip if a character doesn't match.
termin
	camn c,1(b)		;Skip only if the entry length is correct.
	 aos (p)
	popj p,

;Output sixbit word in A as ASCII down BP in D
;followed by terminator in B and a null character.  Clobbers C.
sixstr:	push p,b
	move b,[440600,,a]
sixst1:	ildb c,b
	addi c,40
	idpb c,d
	setz c,
	dpb c,b
	jumpn a,sixst1
sixst2:	pop p,b
	idpb b,d
	push p,d
	setz c,
	idpb c,d
	pop p,d
	popj p,

];IFN $$PFN

IFN $$MERGE,[	;Routine to merge defaults from one filename block with another filename block.

;Given in A and B two AOBJN pointers to filename blocks of input data,
;and in C an AOBJN pointer to an output filename block,
;merge the two input blocks writing the result in the output one.
;The first argument's components take priority over the second argument's.

;D should contain nonzero if a single name specified in the
;first argument should override all names of the second argument.
;This value is stored in MRGFN2 while MERGE is running.
;If MRGFN2 is nonzero, then if the first argument contains only one NAME,
;the second NAME (if any) from the second argument is used as well.
;If MRGFN2 is zero, then if the first argument contains only one NAME,
;only that NAME is used.
.scalar mrgfn2

;On return, C's lh is set to minus the number of words used.
;We skip if the space provided was sufficient.
merge:	push p,mrgfn2
	movem d,mrgfn2
	push p,c
	movei d,":
	pushj p,merge1
	movei d,";
	pushj p,merge1
	movei d,40
	push p,a
	push p,b
	pushj p,msrch
	 jrst merge2
	move a,(p)		;No NAME found in input 1 => copy all NAMEs from input 2.
merge3:	move b,(p)
	pushj p,mcopy
mergex:	pop p,b
	pop p,a
mergx1:	pop p,d			;Pop original C into D.
	caml c,[2,,]
	 jrst [	move c,d
		jrst mergx2]
	sub c,d
	andi c,-1		;C gets number of words pushed onto filename block now in D.
	movns c
	hrlzs c
	hrr c,d			;C gets AOBJN ptr to used portion of filename block.
	aos -1(p)
mergx2:	move d,mrgfn2
	pop p,mrgfn2
cpopj:	popj p,

merge2:	move b,(p)
	pushj p,mcopnm		;One NAME found in input 1 => copy it to output.
	add a,[2,,2]
	pushj p,msrch		;Search for a second one.
	 jrst merge3		;Found => copy all remaining NAMEs from input 1.
	move a,(p)
	skipe mrgfn2		;Not found: if want no default fn2, return with just this one.
	 pushj p,mergln
	jrst mergex

;Search the filename block <- AOBJN ptr in A for an entry whose string
;ends in the character in D.  Skip if NOT found.
;Leave A pointing at the entry if it is found.
;Clobbers B.
;An entry ending in a null character is considered to end with a space.
msrch:	jumpge a,popj1
	move b,1(a)
	ldb b,b
	skipn b
	 movei b,40
	caie b,^X		;Finding either a ^X or a ^Y
	 cain b,^Y		;counts as finding a name.
	  movei b,40
	cain b,(d)
	 popj p,
	add a,[2,,2]
	jrst msrch

popj1:	aos (p)
	popj p,

;Copy all the elements ending in the character in D
;from the filename block <- AOBJN ptr in A if it contains any;
;otherwise, copy all the ones from the filename block <- AOBJN ptr in B.
;D should NOT contain a space.
merge1:	push p,a
	push p,b
	pushj p,msrch
	 jrst merge9
	move a,(p)
	pushj p,msrch
merge9:	 pushj p,mcopy
popbaj:	pop p,b
	pop p,a
	popj p,

;Copy entries from the filename block <- AOBJN ptr in A to that in C.
;Copy all those whose ending character matches that in D.
;When copying NAMEs (D contains a space)
;^X and ^Y entries are processed as well,
;using the filename block <- AOBJN ptr in B.
mcopy:	push p,b
mcopy0:	jumpge c,popbj
	jumpge a,popbj
	move b,1(a)
	ldb b,b
	caie b,^X
	 cain b,^Y
	  caie d,40
	   caia
	    jrst mcopy3
	skipn b
	 movei b,40
	came b,d
	 jrst mcopy1
mcopy3:	move b,(p)
	pushj p,mcopnm
mcopy1:	add a,[2,,2]
	jrst mcopy0

;Copy the filename component that A points to
;into the filename block in C, assuming that B
;contains the filename block to find defaults in (for ^X and ^Y).
mcopnm:	push p,b
	move b,1(a)
	ldb b,b
	caie b,^X
	 cain b,^Y
	  jrst mcopy2
	jumpge c,mcopn1
	move b,(a)
	movem b,(c)
	move b,1(a)
	movem b,1(c)
mcopn1:	add c,[2,,2]
	jrst popbj

;Copy a NAME which is really a ^X or ^Y.
;Get, off the stack, the second arg block
;and copy either its first NAME or its last one.
mcopy2:	exch a,(p)
	push p,a
	pushj p,[cain b,^X
		  jrst mergfn
		 jrst mergln]
	jrst popbaj

;Copy the first NAME from the arg block in A into the output here.
mergfn:	push p,b
	push p,d
	movei d,40
	pushj p,msrch
	 jrst mergf1
	jrst popdbj

;Copy the last NAME from the arg block in A into the output here.
mergln:	push p,b
	push p,d
	movei d,40
	pushj p,msrch		;Don't even consider the first name.  Find it now
	 caia			;so we can start from after it.
	  jrst popdbj
	push p,[0]
mergl1:	add a,[2,,2]
	pushj p,msrch		;Keep searching, and remember the last place we found one.
	 jrst [	movem a,(p)
		jrst mergl1]
	pop p,a
mergf1:	jumpe a,popdbj
	jumpge c,mergf2
	move b,(a)		;Copy that last one into the output.
	movem b,(c)
	move b,1(a)
	movem b,1(c)
mergf2:	add c,[2,,2]
popdbj:	pop p,d
popbj:	pop p,b
	popj p,

];IFN $$MERGE

IFN $$STKMRG,[

;Parse and merge filenames using temprary storage on the stack.

.scalar stkmp1,stkmp2,stkmp3	;Temporaries used by STKMRG.

;Merge two filename strings to produce a new string,
;which is stored into freshly allocated storage.
;The user-provided STRALC routine is used to allocate that new string.
;All other temporary space is allocated on the stack.

;Call with byte pointers to the two strings in A and B.
;A points to the specified string and B points to the defaults.
;On return, A points to the newly allocated string's contents.
;No accumulators except A, B, C and D are touched,
;so the allocation routine can return additional information
;or other sorts of pointers to the string
;in any of the other accumulators.

;You must supply a subroutine named STRALC which should
;allocate a string of a size given in C, and return a byte pointer
;to the string in A.  It should skip if successful,
;and not skip if such a string cannot be allocated
;(length is too great).

;STKMRG skips if STRALC did.

stkmrg:	push p,a
	push p,b
	move d,a
	movsi b,(setz)		;How much space do we need for a filename block for the 1st arg?
	pushj p,rfn
	hrri b,1(p)
	movem b,stkmp1		;Save an AOBJN ptr to table we are allocating.
	hlre c,b
	movns c			;C gets length required.
	hrls c
	move d,-1(p)
	add p,c			;Mark space as in use.
	pushj p,rfn		;Parse the 1st arg into the space allocated.
	move a,stkmp1
	move d,-1(a)
	movsi b,(setz)
	pushj p,rfn		;How much space do we need for a filename block for 2nd arg?
	hrri b,1(p)
	movem b,stkmp2		;Save an AOBJN ptr to table we are allocating.
	hlre c,b
	movns c			;C gets length required.
	hrls c
	add p,c			;Mark space as in use.
	move a,stkmp1
	move d,-1(a)		;Get back our 2nd arg and parse into that space.
	pushj p,rfn
	move a,stkmp1
	move b,stkmp2		;Get the AOBJN ptrs to the two tables in the stack.
	hlre d,a
	hlre c,b
	subi c,2
	addb d,c		;Get (minus) sum of their lengths; allocate filename block
	hrlzs c			;that long to hold the merged filenames.
	hrri c,1(p)
	movem c,stkmp3
	movns d			;D gets length allocated for merged filename block,
	hrls d			;in both halves.
	add p,d			;Allocate the space.
	pushj p,merge		;Now merge into that table.
	movem c,stkmp3		;Save ptr to what was used.
	move b,c
	pushj p,pfnct		;Put length in D.
	move c,d
	pushj p,stralc		;Call user-supplied routine to allocate and put BP in A.
	 jrst stkmr1
	move b,stkmp3
	push p,a
	move d,a
	pushj p,pfn		;Store the ultimate name into the user-supplied string.
	pop p,a
	move b,p		;Deallocate the temporary storage
	sub b,stkmp1
	hrls b
	sub p,b
	sub p,[3,,3]
	aos (p)
	popj p,

;Return non-skipping.
stkmr1:	move b,p		;Deallocate the temporary storage
	sub b,stkmp1
	hrls b
	sub p,b
	sub p,[3,,3]
	popj p,

] ;end $$STKMRG

.end rfnl
