Google
 

Trailing-Edge - PDP-10 Archives - AP-D471B-SB_1978 - utilty.bli
There is 1 other file named utilty.bli in the archive. Click here to see a list.
!***COPYRIGHT (C) 1974, 1975, 1976, 1977 DIGITAL EQUIPMENT CORP., MAYNARD, MASS.***
%%
%
	THIS MODULE CONTAINS ALL THE NON-I/O UTILITY ROUTINES.

	ZERO   - ZEROS BLOCKS OF MEMORY
	CMPASC - COMPARES ASCII OR ASCIZ STRINGS
	CMPVAR - COMPARES VARYING STRINGS
	KEYCHK - CHECKS IF A STRING IS A KEYWORD
	ERTEXT - PRINTS ERROR TEXTS
	GET    - DOES THE CORE UUO FOR ALLOC
	ALLOC  - ALLOCATES WORK AREAS
	FREE   - FREES ALL THE ALLOCATION WORK AREAS
	SEMI   - SCANS FOR A ";" FROM THE TTY
	CNVCHR - CONVERTS A BINARY NUMBER TO A VARYING CHARACTER STRING
	GETREP - GETS A REPORT BLOCK
	BYTOFF - FORMS A BYTE POINTER WITH AN OFFSET
	DUPL   - DUPLICATES CHARACTER STRINGS
	COPYA  - COPIES ASCII STRINGS
	CNVSIX - CONVERTS ASCII TO SIXBIT
	PRTSPC - PRINTS "FOR FILE DEV:FILE.EXT[PPN]"
	CNVBDT - CONVERTS DATE UUO TO CHARACTER STRING
	CNVBTM - COMVERTS MSTIME UUO TO CHARACTER STRING
	MSTIME - DOES MSTIME UUO
	DATE   - DOES DATE UUO
	GETPPN - DOES GETPPN UUO
	PJOB   - DOES PJOB UUO
	INIT   - PERFORMS GENERAL INITIALIZATIONS
	CNVBIN - CONVERTS STRING TO BINARY, REVERSE OF CNVCHR
	ADDBYT - LIKE BYTOFF, BUT ADDS CHARS TO BYTE PTRS


%
%%

MODULE UTILTY (MLIST,FSAVE,TIMER=EXTERNAL(SIX12)) =
BEGIN
	REQUIRE COMMON.BLI;
	REQUIRE ENTRY.BLI;
COMMENT(ZERO);

! SUBROUTINE ZERO
! ========== ====

! THIS ROUTINE ZEROS A BLOCK OF MEMORY.
! LAST MODIFIED ON 26 JUN 74 BY JG.

GLOBAL ROUTINE ZERO (DATAPTR,COUNT) =
BEGIN
	REGISTER XWD;
	MACHOP   BLT = #251;

	XWD<LH> _ .DATAPTR;			! SET UP OVERLAPPING BLT
	XWD<RH> _ .DATAPTR+1;
	(.DATAPTR)<WORD> _ 0;			! ZERO FIRST WORD
	BLT(XWD,.DATAPTR+.COUNT-1)		! ZERO REST OF BLOCK
END;
COMMENT(CMPASC);

! SUBROUTINE CMPASC
! ========== ======

! THIS ROUTINE COMPARES TWO ASCII STRINGS AND RETURNS TRUE IF THEY ARE
! EQUAL, FALSE IF NOT. IF BOTH LENGTHS ARE ZERO, THEY ARE ASSUMED TO
! BE ASCIZ STRINGS. NOTE THAT IF THE STRINGS ARE ASCIZ, THEY ARE
! CONSIDERED EQUAL IF THEY MATCH UP TO BUT NOT INCLUDING THE FIRST NULL
! CHARACTER. THUS THEY NEED NOT BE THE SAME LENGTH AS LONG AS THE SHORTER
! STRING MATCHES THE LONGER FOR THE LENGTH OF THE SHORTER.

! LAST MODIFIED ON 26 AUG 74 BY JG.

GLOBAL ROUTINE CMPASC (PTR1,LEN1,PTR2,LEN2) =
BEGIN
	LOCAL TEMP,TEMP2;

	IF .LEN1 NEQ .LEN2 THEN RETURN FALSE;		! MUST BE SAME LENGTH, EVEN IF ZERO
	IF .LEN1 LSS 0 THEN RETURN FALSE;		! AND CERTAINLY NOT NEGATIVE
	IF .LEN1 EQL 0
	THEN REPEAT					! ASCIZ STRINGS
	BEGIN						! REPEAT UNTIL NULL CHAR
		TEMP _ SCANI(PTR1);
		TEMP2 _ SCANI(PTR2);
		IF .TEMP IS NULL OR .TEMP2 IS NULL	! LEAVE WITHOUT CHECKING THE OTHER CHAR
			THEN RETURN TRUE;
		IF .TEMP ISNOT .TEMP2 THEN RETURN FALSE
	END
	ELSE DECR TEMP FROM .LEN1-1 TO 0		! HERE WE WERE GIVEN LENGTHS
		DO IF SCANI(PTR1) NEQ SCANI(PTR2) THEN RETURN FALSE;
	TRUE
END;
COMMENT(CMPVAR);

! SUBROUTINE CMPVAR
! ========== ======

! THIS ROUTINE COMPARES TWO VARYING CHARACTER STRINGS.
! LAST MODIFIED ON 12 JUL 75 BY JG.

GLOBAL ROUTINE CMPVAR (VAR1,VAR2) =
BEGIN
	LOCAL A,B;
	MAP VARYINGCHAR VAR1:VAR2;

	IF .VAR1[LANGTH] NEQ .VAR2[LANGTH] THEN RETURN FALSE;! LENGTHS MUST BE EQUAL
	A _ .VAR1[LANGTH];
	IF .A LSS 0 THEN RETURN FALSE;			! AND NOT NEGATIVE
	B _ .A/5;					! CALCULATE LENGTH IN WORDS
	IF .B*5 NEQ .A THEN B _ .B+1;			! FUDGE IF NOT A FULL WORD
	INCR A FROM 0 TO .B-1				! WE CAN DO WORD COMPARISONS ON VARYING STRINGS
	DO IF @(VAR1[STRING]+.A) NEQ @(VAR2[STRING]+.A)
		THEN RETURN FALSE;
	TRUE
END;
COMMENT(KEYCHK);

! SUBROUTINE KEYCHK
! ========== ======

! THIS ROUTINE CHECKS IF THE GIVEN VARYING CHARACTER STRING
! IS A KEYWORD. JUST IN CASE THE MATCH CONSTITUTED A PARTIAL
! UNIQUE MATCH, THE ENTIRE MATCHED KEYWORD IS COPIED INTO
! THE TOKEN, SO ROUTINES PRINTING THE TOKEN EXPECTING A
! RECOGNIZABLE KEYWORD THERE WILL NOT BE DISSAPPOINTED.
! THIS IS OK SINCE KEYCHK IS CALLED ONLY WHEN A REAL LIVE
! KEYWORD IS EXPECTED ANYWAY.

! LAST MODIFIED ON 26 AUG 74 BY JG.

GLOBAL ROUTINE KEYCHK (VARPTR,INDEXPTR) =
BEGIN
	LOCAL	MATCH,KPTR,MPTR;
	LABEL	THISONE;
	MAP	VARYINGCHAR VARPTR:KPTR:MPTR;

	MATCH _ 0;					! NO MATCHES YET
	INCR I FROM 0 TO NUMKEYWORDS-1			! LOOK AT ALL THE KEYWORDS
	DO THISONE: BEGIN
		KPTR _ .(.KEYWRD+.I )+1;		! GET PTR TO THIS KEYWORD
		IF .VARPTR[LANGTH] GTR .KPTR[LANGTH]	! TOKEN TOO BIG FOR MATCH?
			THEN LEAVE THISONE;		! YES
		IF CMPASC(VARPTR[STRING]<FIRSTINCR>,.VARPTR[LANGTH],KPTR[STRING]<FIRSTINCR>,.VARPTR[LANGTH])
		THEN IF .MATCH IS 0			! TOKEN MATCHES KEYWORD
			THEN BEGIN			! THIS IS THE FIRST MATCH
				MATCH _ .I;		! REMEMBER IT
				MPTR _ .KPTR		! AND THE POINTER
			END
			ELSE BEGIN			! THIS IS THE SECOND MATCH
				.INDEXPTR _ -1;		! INDICATE AMBIGUOUS KEYWORD
				RETURN FALSE		! NO COMMAND, OF COURSE
			END
	END;
	IF .MATCH ISNOT 0				! THEN WE FOUND A UNIQUE MATCH
	THEN BEGIN
		COPYA(MPTR[STRING]<FIRSTINCR>,VARPTR[STRING]<FIRSTINCR>,.MPTR[LANGTH]); 
		.INDEXPTR _ .MATCH+1;			! TELL CALLER WHICH ONE
		RETURN ..(.KEYWRD +.MATCH);		! HAPPILY RETURN (WITH TRUE IF COMMAND, FALSE IF NOT)
	END;

	.INDEXPTR _ 0;					! NO MATCH AT ALL
	FALSE						! RETURN SHAMEFULLY
END;
COMMENT(ERTEXT);

! SUBROUTINE ERTEXT
! ========== ======

! THIS ROUTINE PRINTS ERROR TEXTS ON THE TTY.
! LAST MODIFIED ON 28 AUG 74 BY JG.

GLOBAL ROUTINE ERTEXT (TEXTNUMBER) =
BEGIN
	EXTERNAL TTYOTS,TTYOTN;

	CASE .TEXTNUMBER-1 OF
	SET
		%1%  TTYOTS(0,PLIT ASCIZ 'UNRECOGNIZED COMMAND: ');
		%2%  TTYOTN(0,PLIT ASCIZ ' COMMAND NOT IMPLEMENTED.');
		%3%  TTYOTN(0,PLIT ASCIZ 'UNABLE TO OBTAIN CORE FOR WORK AREAS.');
		%4%  TTYOTN(0,PLIT ASCIZ 'GARBAGE AFTER COMMAND IGNORED.');
		%5%  TTYOTS(0,PLIT ASCIZ 'REPORT NAME TRUNCATED TO ');
		%6%  TTYOTN(0,PLIT ASCIZ ' CHARACTERS.');
		%7%  TTYOTS(0,PLIT ASCIZ 'REPORT NAME PADDED WITH *''S TO ');
		%8%  TTYOTS(0,PLIT ASCIZ 'EXPECTED ARGUMENT TO ');
		%9%  TTYOTN(0,PLIT ASCIZ ' MISSING.');
		%10% TTYOTN(0,PLIT ASCIZ 'REFERING TO PREVIOUS REPORT OF SAME NAME.');
		%11% TTYOTN(0,PLIT ASCIZ 'DEVICE NAME, FILE NAME, OR EXTENSION TOO LONG');
		%12% TTYOTN(0,PLIT ASCIZ 'MISSING OR INVALID DELIMITER IN FILE SPEC.');
		%13% TTYOTN(0,PLIT ASCIZ 'MUTIPLE FILE NAME OR EXTENSION.');
		%14% TTYOTN(0,PLIT ASCIZ 'UNRECOGNIZABLE FILE SPEC.');
		%15% TTYOTN(0,PLIT ASCIZ 'MISSING DEVICE NAME.');
		%16% TTYOTN(0,PLIT ASCIZ 'MISSING FILE NAME.');
		%17% TTYOTN(0,PLIT ASCIZ 'NO MORE I/O CHANNELS AVAILABLE.');
		%18% TTYOTS(0,PLIT ASCIZ 'LOOKUP ERROR CODE ');
		%19% TTYOTS(0,PLIT ASCIZ 'ENTER ERROR CODE ');
		%20% TTYOTS(0,PLIT ASCIZ ' CANNOT BE OPENED FOR ');
		%21% TTYOTN(0,PLIT ASCIZ 'MULTIPLE COLONS IN FILE SPEC.');
		%22% TTYOTN(0,PLIT ASCIZ 'MUTIPLE PERIODS IN FILE SPEC.');
		%23% TTYOTS(0,PLIT ASCIZ 'ERROR DURING CLOSE ');
		%24% TTYOTS(0,PLIT ASCIZ 'OPTION, ');
		%25% TTYOTN(0,PLIT ASCIZ ', NOT RECOGNIZED.');
		%26% TTYOTN(0,PLIT ASCIZ ', NOT IMPLEMENTED.');
		%27% TTYOTS(0,PLIT ASCIZ 'INVALID CHARACTER OR DELIMITER IN ');
		%28% TTYOTN(0,PLIT ASCIZ ' COMMAND.');
		%29% TTYOTN(0,PLIT ASCIZ ', MUST BE SPECIFIED FIRST.');
		%30% TTYOTN(0,PLIT ASCIZ ', IGNORED AFTER "ALL".');
		%31% TTYOTN(0,PLIT ASCIZ 'ASSUMING "BOTH".');
		%32% TTYOTS(0,PLIT ASCIZ 'PREVIOUS REPORT GIVEN NAME OF ');
		%33% TTYOTN(0,PLIT ASCIZ ' *''S.');
		%34% TTYOTN(0,PLIT ASCIZ ' FILE NOT SPECIFIED.');
		%35% TTYOTS(0,PLIT ASCIZ 'I/O ERROR ');
		%36% TTYOTN(0,PLIT ASCIZ 'NO REPORTS HAVE BEEN CREATED YET.');
		%37% TTYOTN(0,PLIT ASCIZ 'UNEXPECTED EOF IN INPUT FILE.');
		%38% TTYOTS(0,PLIT ASCIZ ' ERROR IN RECORD ');
		%39% TTYOTN(0,PLIT ASCIZ ' IS AMBIGUOUS.');
		%40% TTYOTN(0,PLIT ASCIZ 'ERROR ALLOCATING I/O BUFFERS.');
		%41% TTYOTN(0,PLIT ASCIZ 'INVALID PPN.');
		%42% TTYOTN(0,PLIT ASCIZ 'SFD''S NOT INPLEMENTED.');
		%43% TTYOTN(0,PLIT ASCIZ 'MULTIPLE PPN''S IN FILE SPEC.');
	TES
END;
COMMENT(GET);

! SUBROUTINE GET
! ========== ===

! THIS ROUTINE IS USED BY ALLOC TO DO THE CORE UUO.
! IT RETURNS A POINTER TO A BLOCK OF HOWMUCH WORDS.
! IF THE CORE UUO FAILS, AN ERROR MESSAGE IS
! PRINTED AND GET RETURNS FALSE.
! LAST MODIFIED ON 9 JUL 74 BY JG.

ROUTINE GET (HOWMUCH) =
BEGIN
	REGISTER AC;
	MACHOP	 CALLI = #47;
	MACRO	 COREUUO = CALLI(AC,#11)$;

	AC _ .?.JBFF+.HOWMUCH-1;			! CORE UUO NEEDS TOTAL CORE REQUIREMENT
	IFSKIP COREUUO					! DID WE GET IT?
	THEN BEGIN					! YES
		AC _ .?.JBFF;				! KLUDGY, SAVES A LOCAL VAR
		?.JBFF _ .?.JBFF+.HOWMUCH;		! MUST UPDATE .JBFF
		RETURN (.AC)<WORD>			! FORCE OLD .JBFF TO WORD PTR AND RETURN
	END
	ELSE BEGIN					! NO SUCH LUCK
		ERTEXT(3);				! TELL THE WORLD ABOUT OUR PROBLEMS
		RETURN FALSE				! SUPER DISSAPPOINTMENT
	END
END;
COMMENT(ALLOC);

! SUBROUTINE ALLOC
! ========== =====

! THIS ROUTINE IS THE STORAGE ALLOCATOR FOR THE MCS
! REPORT GENERATOR. IT RETURNS A POINTER TO A
! ZEROED BLOCK OF CORE. IF WORDS AMOUNT OF CORE IS
! NOT OBTAINABLE, ALLOC RETURNS FALSE. THE STORAGE
! MANAGEMENT METHOD (IF IT DESERVES THAT NAME) IS QUITE
! SIMPLE - EACH ALLOCATION AREA HAS THREE WORDS OVERHEAD
! AT THE BEGINNING. THE FIRST IS THE POINTER TO THE
! NEXT ALOCATION AREA, THE SECOND IS THE HIGHEST ADDRESS
! USABLE IN THIS AREA, THE THIRD IS THE ADDRESS OF THE
! LAST USED WORD IN THE AREA. STORAGE IS ALLOCATED BY
! UPDATING THE LASTUSED WORD. THE REASON WE CAN GET AWAY
! WITH SOMETHING THIS SIMPLE IS THAT THE STORAGE IS NEVER
! FREED BY THE ROUTINE REQUESTING IT, LARGELY BECAUSE
! ONCE THE STORAGE IS ALLOCATED, IT IS NEEDED UNTIL THE
! GENERATION OF ALL THE REPORTS IS FINISHED, I.E., WHEN
! THE GO COMMAND ENDS. TO ADD TO THE SIMPLICITY, WHEN
! THE GO COMMAND ENDS, ALL THE STORAGE CAN BE FREED. THIS
! SAVES A LOT OF BOOKKEEPPING. NOW, IN ORDER FOR OUR FAITHFUL
! READER TO UNDERSTAND HOW WE HAVE MANAGED TO COMPLICATE
! SUCH A SIMPLE SCHEME, HE MUST REALIZE THAT TWO PROCESSES
! TAKE PLACE ASYNCHRONOUSLY IN THIS ROUTINE - THE FIRST IS
! THE PARCELLING OUT OF THE STORAGE IN THE AREAS (AND GETTING
! NEW AREAS WHEN NECESSARY) AND THE SECOND IS THE UPDATTING
! OF CURFRE WHICH ALWAYS POINTS TO WHERE WE START LOOKING
! FOR ROOM. THE STORAGE IS PARCELLED OUT BY STARTING AT CURFRE
! AND LOOKING FOR AN AREA THAT HAS THE ROOM FOR THE REQUEST.
! IF ONE IS FOUND, LASTUSED IS UPDATED AND THE POINTER RETURNED.
! IF ONE IS NOT FOUND, A NEW ALLOCATION AREA IS OBTAINED FROM
! GET, IT'S SIZE BEING DETERMINED BY THE SIZE OF THE REQUEST. IF
! IT WILL FIT IN THE "STANDARD" AREA THEN COREINCR IS THE SIZE
! OF THE AREA, IF IT WON'T, THEN THE REQUESTED AMOUNT PLUS THE
! 3 OVERHEAD WORDS IS USED FOR THE AREA SIZE. ALL NEW AREAS
! ARE PLACED IN THE CHAIN IMMEDIATELY AFTER CURFRE. ITS PLACED
! THERE BECAUSE WE HAVE ONLY 2 POINTERS TO AREAS - FIRFRE AND
! CURFRE, AND WE DON'T WANT TO PLACE IT AT THE BEGINNING.
! QUITE INDEPENDENT OF ALL THIS IS THE UPDATTING OF CURFRE.
! THIS OCCURS ONLY WHEN THE CURRENT ALLOCATION AREA HAS LESS
! THAN 10 WORDS LEFT. JUST TO BE TIDY, THE SIZE OF THE FIRST
! ALOCATION AREA IS SUCH THAT THE LOW SEG IS ROUNDED TO A
! PAGE BOUNDARY.

! LAST MODIFED ON 23 AUG 74 BY JG.

GLOBAL ROUTINE ALLOC(WORDS) =
BEGIN
	LABEL SEARCH;
	LOCAL THISAREA,AMOUNT,NEXT;
	MACRO INIT(BLOCK,SIZE) =
		BLOCK[NEXTAREA] _ NULL;
		BLOCK[HIGHADDR] _ (.BLOCK+SIZE-1)<ADDR>;
		BLOCK[LASTUSED] _ (.BLOCK+2)<ADDR>$;
	MAP   AREA FIRFRE:CURFRE:THISAREA:NEXT;

	IF .WORDS EQL 0 OR .FIRFRE IS NULL		! FIRST CALL EVER OR FIRST AFTER RESET UUO
	THEN BEGIN
		AMOUNT _ .?.JBFF MOD 512;		! ROUND UPWARD TO PAGE BOUNDARY
		IF .AMOUNT ISNOT 0
			THEN AMOUNT _ 512 - .AMOUNT;
		CURFRE _ FIRFRE _ GET(COREINCR+.AMOUNT);! NOW GET THE CORE
		IF .CURFRE EQL NULL THEN RETURN FALSE;	! TOO BAD
		INIT(CURFRE,COREINCR+.AMOUNT);		! INITIALIZE THE AREA
		RETURN .CURFRE
	END;

	THISAREA _ .CURFRE;				! THIS HOPEFULLY WILL BE WHERE WE PUT IT

	IF .CURFRE[LASTUSED]+.WORDS GTR .CURFRE[HIGHADDR]
	THEN BEGIN					! NOPE, THERE'S NO ROOM
		AMOUNT _ IF .WORDS LEQ COREINCR-3	! IN CASE WE NEED TO GETA NEW AREA
			THEN COREINCR			! STANDARD WILL BE ENOUGH
			ELSE .WORDS+3;			! NOT BIG ENOUGH, REMEMBER THE HEADER!
		IF .CURFRE[NEXTAREA] EQL NULL		! NO AREAS LEFT ON CHAIN
		THEN BEGIN
			THISAREA _ GET(.AMOUNT);	! GET A NEW ONE
			IF .THISAREA EQL NULL THEN RETURN FALSE;
			CURFRE[NEXTAREA] _ .THISAREA;	! PLACE IT ON THE CHAIN
			INIT(THISAREA,.AMOUNT);		! INITIALIZE IT
			IF .CURFRE[HIGHADDR] - .CURFRE[LASTUSED] LSS 10
				THEN CURFRE _ .THISAREA;! OLD ONE HAD LESS THE 10 FREE WORDS, DISCARD IT
		END
		ELSE SEARCH: BEGIN			! THERE MIGHT BE ROOM SOMEWHERE
			NEXT _ .CURFRE[NEXTAREA];	! LOOK AT NEXT AREA
			WHILE .NEXT NEQ NULL		! AND KEEP LOOKING AT THE NEXT ONE
			DO BEGIN
				IF .NEXT[LASTUSED]+.WORDS LEQ .NEXT[HIGHADDR]
				THEN BEGIN		! WE FOUND SOME ROOM
					THISAREA _ .NEXT;
					IF .CURFRE[HIGHADDR] - .CURFRE[LASTUSED] LSS 10
						THEN CURFRE _ .CURFRE[NEXTAREA];! DISCARD CURRENT AREA
					LEAVE SEARCH
				END;
				NEXT _ .NEXT[NEXTAREA]	! NEXT AREA
			END;
			THISAREA _ GET(.AMOUNT);	! NEED A NEW AREA
			IF .THISAREA EQL NULL THEN RETURN FALSE;
			THISAREA[NEXTAREA] _ .CURFRE[NEXTAREA];! INSERT AFTER CURFRE IN CHAIN
			CURFRE[NEXTAREA] _ .THISAREA;
			THISAREA[HIGHADDR] _ (.THISAREA+.AMOUNT-1)<ADDR>;! INITIALIZE AREA
			THISAREA[LASTUSED] _ (.THISAREA+2)<ADDR>;
			IF .CURFRE[HIGHADDR] - .CURFRE[LASTUSED] LSS 10
				THEN CURFRE _ .THISAREA;! DISCARD OLD AREA
		END
	END;

	NEXT _ .THISAREA[LASTUSED]+1;			! FIRST FREE WORD
	THISAREA[LASTUSED] _ .THISAREA[LASTUSED]+.WORDS;! UPDATE LASTUSED
	ZERO(.NEXT,.WORDS);				! MAKE REAL SURE IT CONTAINS ZEROS
	(.NEXT)<WORD>					! RETURN WORD POINTER
END;

COMMENT(FREE);

! SUBROUTINE FREE
! ========== ====

! THIS ROUTINE EMPTIES ALL THE ALLOCATIONS AREAS
! BY SETING THE LASTUSED FIELD OF EACH AREA
! HEADER TO THE THIRD WORD OF THAT AREA.
! LAST MODIFIED ON 12 JUL 74 BY JG.

GLOBAL ROUTINE FREE =
BEGIN
	LOCAL NEXT;
	MAP   AREA NEXT;

	IF .FIRFRE IS NULL THEN RETURN;			! FREE MAY BE CALLED BEFORE ANY ALLOCATIONS
	NEXT _ .FIRFRE;
	WHILE .NEXT ISNOT NULL				! FOR ALL THE AREAS
	DO BEGIN
		NEXT[LASTUSED] _ (.NEXT+2)<ADDR>;	! RESET LASTUSED
		NEXT _ .NEXT[NEXTAREA]			! NEXT AREA
	END;
	CURFRE _ .FIRFRE				! RESET CURFRE
END;
COMMENT(SEMI);

! SUBROUTINE SEMI
! ========== ====

! THIS ROUTINE READS AND THROWS AWAY TOKENS FROM
! THE TTY UNTIL IT FINDS A ";". IT RETURNS TRUE IF IT
! ENCOUNTERS ANYTHING BEFORE IT SAW THE ";", FALSE OTHERWISE.
! LAST MODIFIED ON 12 JUL 74 BY JG.

GLOBAL ROUTINE SEMI =
BEGIN
	LOCAL GARBAGE,TOKEN;
	MAP   VARYINGCHAR TOKEN;
	EXTERNAL TTYINT;

	GARBAGE _ FALSE;				! NO GARBAGE YET
	REPEAT BEGIN
		TOKEN _ TTYINT();			! GET A TOKEN
		IF .TOKEN[STRING] IS ';' THEN RETURN .GARBAGE;! FOUND A ";"
		GARBAGE _ TRUE				! NOT A ";", WE SAW GARBAGE
	END
END;
COMMENT(CNVCHR);

! SUBROUTINE CNVCHR
! ========== ======

! THIS ROUTINE CONVERTS A BINARY NUMBER INTO A
! VARYING CHARACTER STRING USING THE SPECIFIED
! BASE. THE OUTPUT AREA IS ASSUMED TO BE AT
! LEAST 5 WORDS LONG (THIS INCLUDES THE WORD FOR
! THE HEADER). NO CHECKING OF THE BASE IS
! DONE AND VERY LARGE NUMBERS IN BASE 2 MAY NOT
! FIT IN 20 CHARACTERS (THEY SHOULD BE PRINTED AS
! AN OCTAL WORD ANYWAY). A DEFINITE IMPROVEMENT
! IN THE OVERALL PERFORMANCE OF MCSREP CAN BE
! ACHIEVED BY RECODING THIS ROUTINE IN MACRO-10.
! LAST MODIFIED ON 11 JUL 74 BY JG.

GLOBAL ROUTINE CNVCHR (NUMBER,CHAROUTPTR,BASE) =
BEGIN
	OWN NEGATIVE,OUTPUT,OUTPTR,N,B;			! THESE MUST BE OWN OR DIVIDE MUST BE A FUNCTION

	% THIS IS THE ROUTINE THAT DOES ALL THE WORK. %

	ROUTINE DIVIDE =
	BEGIN
		LOCAL REMAINDER;
		IF .N IS 0				! HAVE WE REACHED THE "BOTTOM"
		THEN BEGIN				! YES
			IF .NEGATIVE			! CHECK IF NUMBER WAS NEGATIVE
			THEN BEGIN			! YES, IT WAS
				REPLACEI(OUTPUT,"-");	! OUTPUT A "-"
				.OUTPTR _ 1;		! ONE CHAR IN OUTPUT
			END;
			RETURN				! NOW START CLIMBING UP THE STACK, OUTPUTTING CHARS AS WE GO
		END;
		REMAINDER _ .N MOD .B;			! GET NUMBER FOR THIS PRINT POSITION
		N _ .N/.B;				! GO TO NEXT NUMBER POSITION
		DIVIDE();				! DO THE REST OF THE NUMBER
		REPLACEI(OUTPUT,.REMAINDER+"0");	! NOW OUTPUT THIS CHAR
		.OUTPTR _ ..OUTPTR+1			! INCREMENT THE CHAR COUNT
	END;						! CONTINUE UP THE STACK

	% ALL THIS IS JUST SETUP FOR DIVIDE %

	ZERO(.CHAROUTPTR,5);				! MAKE SURE WE HAVE VIRGIN WORK AREA
	OUTPUT _ (.CHAROUTPTR+1)<FIRSTINCR>;		! PTR TO OUTPUT AREA
	OUTPTR _ .CHAROUTPTR;				! POINTER TO LENGTH OF OUTPUT AREA
	IF .NUMBER IS 0					! SPECIAL CASE THIS
	THEN BEGIN
		REPLACEI(OUTPUT,"0");
		.CHAROUTPTR _ 1;
		RETURN
	END;
	IF .NUMBER LSS 0
		THEN NEGATIVE _ TRUE			! REMEMBER THAT IT WAS NEGATIVE
		ELSE NEGATIVE _ FALSE;
	N _ ABS(.NUMBER);				! MAKE SURE NUMBER IS NON-NEGATIVE
	B _ .BASE;					! COPY BASE INTO OWN VAR
	DIVIDE();					! OFF WE GO!
END;
COMMENT(GETREP);

! SUBROUTINE GETREP
! ========== ======

! THIS ROUTINE EITHER CAUSES CURREP TO POINT TO A
! NEW REPORT BLOCK OR INSURES THAT CURREP POINTS TO AN
! OLD ONE BY ALLOCATING ONE IF FIRREP IS NULL. IT
! RETURNS TRUE IF NO PROBLEMS, FALSE IF THE ALLOCATION
! FAILS.
! LAST MODIFIED ON 1 AUG 74 BY JG.

GLOBAL ROUTINE GETREP (PARM) =
BEGIN
	LOCAL TEMP;
	MAP   REPBLK FIRREP:CURREP:TEMP;

	IF .PARM IS OLD					! THEN JUST CHECK FOR EXISTING REPORT BLOCK
	THEN IF .FIRREP ISNOT NULL THEN RETURN TRUE;	! IF NONE, THEN PROCEED LIKE NEW
	TEMP _ ALLOC(REPORTBLKSIZ);			! GET A REPORT BLOCK
	IF .TEMP IS NULL THEN RETURN FALSE;
	IF .FIRREP IS NULL				! MUST CREAT START OF CHAIN
	THEN CURREP _ FIRREP _ .TEMP
	ELSE BEGIN
		IF .CURREP[NEXTREPORT] ISNOT NULL	! MUST LINK TO NEXT REPORT
			THEN TEMP[NEXTREPORT] _ .CURREP[NEXTREPORT];
		CURREP[NEXTREPORT] _ .TEMP;		! LINK FROM PREVIOUS REPORT
		CURREP _ .TEMP				! SET CURREP
	END;
	TRUE
END;
COMMENT(BYTOFF);

! SUBROUTINE BYTOFF
! ========== ======

! THIS ROUTINE RETURNS A BYTE POINTER, OFFSET
! A SPECIFIED AMOUNT OF CHARACTERS FROM THE
! GIVEN ADDRESS. IT IS ASSUMED THAT THE
! RETURNED POINTER WILL BE USED IN INCREMENT OPERATIONS.
! LAST MODIFIED ON 1 AUG 74 BY JG.

GLOBAL ROUTINE BYTOFF (ADDRESS,OSET) =
BEGIN
	LOCAL T1,T2,T3;
	T1 _ T2 _ 0;
	T1<S> _ 7;					! BYTE SIZE IS ASCII
	T3 _ .OSET MOD 5;				! HOW MANY EXTRA CHARS NEEDED?
	IF .T3 IS 0					! FUDGE THINGS FOR FIRST CHAR IN WORD
	THEN BEGIN
		ADDRESS _ .ADDRESS-1;
		T3 _ 5
	END;
	T2<P> _ (6-.T3)*7+1;				! ADJUST P FIELD TO RIGHT AMOUNT OF BITS
	.T1 + .T2 + (.ADDRESS)<ADDR>+.OSET/5		! PUT IT ALL TOGETHER
END;
COMMENT(DUPL);

! SUBROUTINE DUPL
! ========== ====

! THIS ROUTINE DUPLICATES A CHARACTER (RIGHT JUSTIFIED
! IN THE INPUT ARGUMENT) USING THE GIVEN BYTE
! POINTER AND RETURNS THE INCREMENT TYPE BYTE
! POINTER TO THE NEXT CHARACTER.
! LAST MODIFIED ON 12 JUL 74 BY JG.

GLOBAL ROUTINE DUPL (BYTEPTR,CHAR,COUNT) =
BEGIN
	DECR I FROM .COUNT-1 TO 0
		DO REPLACEI(BYTEPTR,.CHAR);
	.BYTEPTR
END;
COMMENT(COPYA);

! SUBROUTINE COPYA
! ========== =====

! THIS ROUTINE COPIES ASCII STRINGS. IF THE COUNT
! IS ZERO, THE SOURCE IS ASSUMED TO BE AN ASCIZ STRING.
! LAST MODIFIED ON 29 JUL 74 GY JG.

GLOBAL ROUTINE COPYA (SRCE,DST,CNT) =
BEGIN
	LOCAL T;
	IF .CNT EQL 0
	THEN REPEAT BEGIN				! ASCIZ STRING
		T _ SCANI(SRCE);
		IF .T EQL NULL THEN RETURN .CNT;	! RETURN THE COUNT
		REPLACEI(DST,.T);			! COPY TO DESTINATION
		CNT _ .CNT+1				! NOT NICE TO USE PARAMETER FOR TEMPORARY, BUT ...
	END
	ELSE DECR I FROM .CNT-1 TO 0			! STRING WITH COUNT
		DO COPYII(SRCE,DST);
	.CNT						! RETURN THE COUNT
END;
COMMENT(CNVSIX);

! SUBROUTINE CNVSIX
! ========== ======

! THIS ROUTINE CONVERTS UP TO SIX ASCII CHARACTERS
! TO SIXBIT AND STUFFS THEM INTO ONE WORD,
! WHICH IS RETURNED. ILLEGAL SIXBIT CHARACTERS ARE
! CONVERTED TO \ (BACKSLASH).
! LAST MODIFIED ON 17 JUL 74 BJ JG.

GLOBAL ROUTINE CNVSIX(ASCBP,COUNT) =
BEGIN
	LOCAL SIXTEMP,SIXBP,CHAR;

	COUNT _ IF .COUNT LEQ 6 THEN .COUNT-1 ELSE 5;
	SIXBP _ SIXTEMP<36,6>;				! SIXBIT PTR TO TEMP
	SIXTEMP _ 0;					! START WITH ALL BLANKS
	DECR I FROM .COUNT TO 0
	DO BEGIN
		CHAR _ SCANI(ASCBP);			! GET A CHAR
		IF .CHAR GTR #140 AND .CHAR LSS #173	! IS CHAR LOWER CASE LETTER?
		THEN CHAR _ .CHAR - #40;		! YES, CONVERT TO UPPER CASE
		IF .CHAR LSS #40 OR .CHAR EQL #140 OR .CHAR GTR #172
		THEN CHAR _ "\";			! CHAR IS NOT GOOD
		REPLACEI(SIXBP,.CHAR- #40);		! CONVERT TO SIXBIT AND INSERT
	END;
	.SIXTEMP
END;
COMMENT(PRTSPC);

! SUBROUTINE PRTSPC
! ========== ======

! THIS ROUTINE PRINTS "FOR FILE DEV:FILE.EXT[PPN]"
! ON THE TTY. USED FOR ERROR MESSAGES.
! LAST MODIFIED ON 16 AUG 74 BY JG.

GLOBAL ROUTINE PRTSPC (CHANNEL) =
BEGIN
	LOCAL EXT,FILEPTR,NUM[5];
	MAP   FILBLK FILEPTR;
	EXTERNAL TTYOTC,TTYOSX,TTYOTS,TTYOTN;

	TTYOTS(0,PLIT ASCIZ ' FOR FILE ');		! PRINT EASY PART
	FILEPTR _ .CHANLS[.CHANNEL];			! GET POINTER TO FILE BLOCK
	TTYOSX(.FILEPTR[DEVICE]); TTYOTC(":");		! PRINT THE DEVICE
	TTYOSX(.FILEPTR[FILENAME]);			! AND THE FILENAME (WHICH COULD BE NULL, WE DON'T CARE)
	IF (EXT _ 0; EXT<LH> _ .FILEPTR[EXTENSION]) NEQ NULL
	THEN BEGIN					! PRINT THE EXTENSION ONLY IF NON-NULL
		TTYOTC(".");
		TTYOSX(.EXT);
	END;
	IF .FILEPTR[PPNUM] ISNOT .USRPPN		! PRINT PPN ONLY IF NOT LOGIN PPN
	THEN BEGIN
		TTYOTC("[");
		CNVCHR(.FILEPTR[PPNUM]<LH>,NUM,8);
		TTYOVR(NUM);
		TTYOTC(",");
		CNVCHR(.FILEPTR[PPNUM]<RH>,NUM,8);
		TTYOVR(NUM);
		TTYOTC("]")
	END;
	TTYOTN(0,PLIT ASCII '.')
END;
COMMENT(CNVBDT);

! SUBROUTINE CNVBDT
! ========== ======

! THIS ROUTINE CONVERTS THE RESULT OF THE DATE
! UUO TO "MON XX, 197X".
! LAST MODIFIED ON 30 JUL 74 BY JG.

GLOBAL ROUTINE CNVBDT(BINDAT,OUTPTR) =
BEGIN
	BIND	MONTHS = PLIT('JAN','FEB','MAR','APR','MAY','JUN',
			      'JUL','AUG','SEP','OCT','NOV','DEC');
	LOCAL	YEAR,MONTH,DAY,PTR,NUM[5];

	YEAR _ .BINDAT/372+1964;			! BASE YEAR IS 1964
	BINDAT _ .BINDAT MOD 372;
	MONTH _ .BINDAT/31;				! GET MONTH
	DAY _ (.BINDAT MOD 31)+1;			! AND DAY

	PTR _ MONTHS[.MONTH]<FIRSTINCR>;
	DECR I FROM 2 TO 0
		DO COPYII(PTR,OUTPTR);			! COPY MONTH TO OUTPUT
	REPLACEI(OUTPTR," ");
	CNVCHR(.DAY,NUM,10);				! CONVERT DAY TO CHAR
	PTR _ NUM[STRING]<FIRSTINCR>;
	COPYII(PTR,OUTPTR);				! COPY IT TO OUTPUT
	IF .DAY GEQ 10
		THEN COPYII(PTR,OUTPTR);		! THERE IS A SECOND CHAR
	REPLACEI(OUTPTR,","); REPLACEI(OUTPTR," ");
	CNVCHR(.YEAR,NUM,10);				! CONVERT YEAR TO CHAR
	PTR _ NUM[STRING]<FIRSTINCR>;
	DECR I FROM 3 TO 0
		DO COPYII(PTR,OUTPTR);			! COPY YEAR TO OUTPUT
	IF .DAY LSS 10 THEN REPLACEI(OUTPTR," ");	! OTHER PEOPLE COUNT ON FIELD BEGIN 12 CHARS
	.OUTPTR
END;
COMMENT(CNVBTM);

! SUBROUTINE CNVBTM
! ========== ======

! THIS ROUTINE CONVERTS THE RESULT OF THE MSTIME
! UUO TO "XX:XX:XX".
! LAST MODIFIED ON 30 JUL 74 BY JG.

GLOBAL ROUTINE CNVBTM(BINTIM,OUTPTR) =
BEGIN
	LOCAL HOUR,MINUTE,SECOND,PTR,NUM[5];

	HOUR _ .BINTIM/3600000;				! GET HOURS
	BINTIM _ .BINTIM MOD 3600000;
	MINUTE _ .BINTIM/60000;				! AND MINUTES
	SECOND _ (.BINTIM MOD 60000)/1000;		! AND SECONDS

	IF .HOUR IS 0
	THEN OUTPTR _ DUPL(.OUTPTR," ",3)		! PAD HOUR WITH BLANKS
	ELSE BEGIN
		IF .HOUR LSS 10 THEN REPLACEI(OUTPTR," ");! LEFT JIUSTIFY HOUR
		CNVCHR(.HOUR,NUM,10);			! CONVERT TO CHAR
		PTR _ NUM[STRING]<FIRSTINCR>;
		COPYII(PTR,OUTPTR);			! COPY TO OUTPUT
		IF .HOUR GEQ 10
			THEN COPYII(PTR,OUTPTR);	! ANOTHER CHAR TO OUTPUT
		REPLACEI(OUTPTR,":");
	END;

	IF .MINUTE IS 0
	THEN BEGIN
		IF .HOUR IS 0				! ONLY ZAP FIELD IF HOUR IS ZERO
			THEN REPLACEI(OUTPTR," ")
			ELSE REPLACEI(OUTPTR,"0");
		REPLACEI(OUTPTR,"0");			! BUT ALWAYS PLACE AT LEAST ONE "0" FOR MINUTES
		REPLACEI(OUTPTR,":")
	END
	ELSE BEGIN
		IF .MINUTE LSS 10
		THEN IF .HOUR IS 0			! SAME TYPE KLUDGE 0 HOUR
			THEN REPLACEI(OUTPTR," ")
			ELSE REPLACEI(OUTPTR,"0");
		CNVCHR(.MINUTE,NUM,10);			! CONVERT TO CHAR
		PTR _ NUM[STRING]<FIRSTINCR>;
		COPYII(PTR,OUTPTR);			! COPY TO OUTPUT
		IF .MINUTE GEQ 10
			THEN COPYII(PTR,OUTPTR);	! AND THE OTHER TOO
		REPLACEI(OUTPTR,":")
	END;

	IF .SECOND IS 0
	THEN OUTPTR _ DUPL(.OUTPTR,"0",2)		! THIS ONE IS EASY
	ELSE BEGIN
		IF .SECOND LSS 10
			THEN REPLACEI(OUTPTR,"0");	! AND SO IS THIS
		CNVCHR(.SECOND,NUM,10);			! CONVERT TO CHAR
		PTR _ NUM[STRING]<FIRSTINCR>;
		COPYII(PTR,OUTPTR);			! COPY TO OUTPUT
		IF .SECOND GEQ 10
			THEN COPYII(PTR,OUTPTR)		! PICK LAST CHAR IN SECOND
	END;

	.OUTPTR
END;
COMMENT(MSTIME);

! SUBROUTINE MSTIME
! ========== ======

! THIS ROUTINE DOES THE MSTIME UUO.
! LAST MODIFIED ON 29 JUL 74 BY JG.

GLOBAL ROUTINE MSTIME =
BEGIN
	MACHOP	CALLI = #47;
	REGISTER AC;
	CALLI(AC,#23);
	.AC
END;
COMMENT(DATE);

! SUBROUTINE DATE
! ========== ====

! THIS ROUTINE DOES THE DATE UUO.
! LAST MODIFIED ON 29 JUL 74 BY JG.

GLOBAL ROUTINE DATE =
BEGIN
	MACHOP	CALLI = #47;
	REGISTER AC;
	CALLI(AC,#14);
	.AC
END;
COMMENT(GETPPN);

! SUBROUTINE GETPPN
! ========== ======

! THIS ROUTINE DOES THE GETPPN UUO.
! LAST MODIFIED ON 29 JUL 74 BY JG.

GLOBAL ROUTINE GETPPN =
BEGIN
	MACHOP	CALLI = #47;
	REGISTER AC;
	IFSKIP CALLI(AC,#24) THEN ELSE;
	.AC
END;
COMMENT(PJOB);

! SUBROUTINE PJOB
! ========== ====

! THE ROUTINE DOES THE PJOB UUO.
! LAST MODIFIED ON 29 JUL 74 BY JG.

GLOBAL ROUTINE PJOB =
BEGIN
	MACHOP	CALLI = #47;
	REGISTER AC;
	CALLI(AC,#30);
	.AC
END;
COMMENT(INIT);

! SUBROUTINE INIT
! ========== ====

! THIS ROUTINE INITIALIZES EVERYTHING THAT HAS TO
! BE INITIALIZED EXCEPT CHANLS, WHICH IS DONE BY
! RESET.
! LAST MODIFIED ON 27 AUG 74 BY JG.

GLOBAL ROUTINE INIT(DORESET) =
BEGIN
	IF .USRPPN IS 0					! THIS MUST BE FIRST CALL
	THEN BEGIN
		USRPPN _ GETPPN();			! GET THE PPN
		USRJBN _ PJOB();			! AND THE JOB NUMBER
		CNVBDT(DATE(),CHRDAT<FIRSTINCR>);	! SET TODAY'S DATE
		CNVBTM(MSTIME(),CHRTIM<FIRSTINCR>)	! AND NOW'S TIME
	END;
	INCHNL _ OTCHNL _ -1;				! RESET INPUT AND OUTPUT CHANNELS
	CURREP _ FIRREP _ NULL;
	FREE();
	IF .DORESET					! RESET UUO?
	THEN BEGIN
		RESET();
		CURFRE _ FIRFRE _ NULL			! ONLY CLEAR AREA POINTERS AFTER RESET UUO
	END
END;
COMMENT(CNVBIN);

! SUBROUTINE CNVBIN
! ========== ======

! THIS ROUTINE CONVERTS A VARYING STRING TO A
! BINARY NUMBER USING THE SPECIFIED BASE.
! IF THE STRING IS INVALID, THE NUMBER
! IS SET TO ZERO AND FALSE IS RETURNED.
! LAST MODIFIED ON 15 AUG 74 BY JG.

GLOBAL ROUTINE CNVBIN(VARPTR,BASE,BINPTR) =
BEGIN
	LOCAL	CHAR,PTR;
	MAP	VARYINGCHAR VARPTR;

	.BINPTR _ 0;					! CLEAR OUTPUT NUMBER
	PTR _ VARPTR[STRING]<FIRSTINCR>;		! PTR TO INPUT STRING
	DECR I FROM .VARPTR[LANGTH]-1 TO 0
	DO BEGIN
		CHAR _ SCANI(PTR);			! GRAB SOMETHING AND STARE AT IT
		IF .CHAR LSS "0" OR .CHAR GTR "9"	! IS IT A NUMBER
		THEN BEGIN				! NO
			.BINPTR _ 0;			! CLEAR NUMBER
			RETURN FALSE			! RETURN BAD NEWS
		END;
		IF (CHAR _ .CHAR-"0") GEQ .BASE		! CONVERT TO BINARY AND CHECK BASE CONTRAINT
		THEN BEGIN				! CHAR TOO BIG FOR BASE (I.E., "9" IN OCTAL)
			.BINPTR _ 0;			! CLEAR NUMBER
			RETURN FALSE			! AGAIN RETURN WITH BAD NEWS
		END;
		.BINPTR _ ..BINPTR*.BASE+.CHAR		! PLACE CHAR AS BINARY PART OF OUTPUT WORD
	END;
	TRUE
END;
COMMENT(ADDBYT);

! SUBROUTINE ADDBYT
! ========== ======

! THIS ROUTINE TAKES A BYTE POINTER AND ADDS TO
! IT THE SPECIFIED NUMBER OF CHARS. NOTE THAT
! THIS ROUTINE DIFFERS FROM BYTOFF IN THAT BYTOFF
! ASSUMES ITS ARGUMENT IS A WORD ADDRESS.
! LAST MODIFIED ON 23 AUG 74 BY JG.

GLOBAL ROUTINE ADDBYT (BYTPTR,COUNT) =
BEGIN
	LOCAL	TEMP,EXTRA;

	TEMP _ BYTOFF(.BYTPTR,.COUNT);			! DO THE EASY PART FIRST
	EXTRA _ 5-.BYTPTR<P>/5;				! SEE HOW MANY EXTRA CHARS IN ORIGINAL BYTE PTR
	DECR I FROM .EXTRA-1 TO 0
		DO INCP(TEMP);				! INCREMENT THE BYTE PTR THAT MANY
	.TEMP
END;

END ELUDOM; ! END OF UTILITY MODULE ...