      PROGRAM LYME
C
C     NAME:    LYME
C
C     AUTHOR:  DAVID PLATT
C              HONEYWELL LOS ANGELES DEVELOPMENT CENTER
C
C     DATE:    4/25/78
C
C     PROGRAM READS FILE ':LMMON.:SYS' WHICH WAS GENERATED BY THE LMMON
C     GHOST JOB (ALONG WITH THE LMMONAID PATCHES TO THE CP-V MONITOR)
C     AND PRODUCES A REPORT OF ALL (OR SELECTED) LOAD MODULES EXECUTED
C     DURING THE TIME PERIOD THAT MONITORING WAS IN EFFECT.  DATA
C     CAPTURED AND REPORTED INCLUDES: LOAD MODULE NAME AND ACCOUNT
C     NUMBER;  NUMBER OF TIMES USED;  CPU TIME (TOTAL AND AVERAGE PER
C     USE);  SYSTEM CALS ISSUED (DITTO);  PHYSICAL I/O OPERATIONS
C     PERFORMED (DITTO);  AND TOTAL WALL-CLOCK (REAL) TIME SPENT IN
C     THE PROGRAM (DITTO).  LYME WILL PROPERLY HANDLE SITUATIONS WHERE
C     ONE PROGRAM M:LINK'S TO ANOTHER (I.E., LYNX M:LINK'S TO THE
C     LOADER);  THE TIME, CALS, I/O'S, ETC. USED BY THE LINKED-TO
C     PROGRAM ARE NOT INCLUDED IN THE TOTAL FOR THE LINKED-FROM
C     PROGRAM (COBOL PROGRAMMERS USING NON-CO-RESIDENT SORT TAKE
C     NOTE!).  LYME PRODUCES 11 REPORTS, EACH ONE BEING SORTED BY
C     ONE OF THE ABOVE-LISTED STATISTICS.
C
      INCLUDE C:LYME
      CALL INIT
      CALL READIN
      CALL CALC
      CALL CLEANUP
      CALL REPORT
      CALL EXIT
      END
      SUBROUTINE INIT
C
C     SUBROUTINE INIT INITIALIZES THE DATA AREAS FOR LYME
C     PROCESSING AND QUERIES THE USER FOR A LIST OF LOAD
C     MODULES/ACCOUNTS TO BE REPORTED ON.
C
      INCLUDE C:LYME
      CHARACTER*(45) SELECT
      CHARACTER*8 FILEACCT
      NPROCS=0
      CALL TABINIT
      PRINT (108, 24)
      READ (105, 28) FILEACCT
      IF (FILEACCT .EQ. ' ') FILEACCT = ':SYS'
24    FORMAT (' WHERE IS THE :LMMON FILE? (DEFAULT = :SYS)')
28    FORMAT (A8)
      OPEN (1, NAME = ':LMMON', STATUS = 'OLD', ACCESS = 'SEQUENTIAL',
     + USAGE = 'INPUT,SHARED', ERR = 900, ACCOUNT = FILEACCT)
      PRINT (108, 30)
30    FORMAT (' WHAT LOAD MODULES?  SPECIFY AS LMN.ACCOUNT - '
     + /' IF LMN OMITTED, SELECTS ALL IN SPECIFIED ACCOUNT;'
     + /' IF ACCOUNT OMITTED, SELECTS :SYS.  ENTER A BLANK'
     + /' LINE TO GET ALL LOAD MODULES IN ALL ACCOUNTS.')
      DO 50 I=1,SELMAX
35    READ (105,40) SELECT
40    FORMAT (A40)
      IF (SELECT .EQ. ' ') GOTO 60
      LMNLIST(I) = ' '
      ACCTLIST(I) = ' '
      DO 41 J=1,40
      IF (SELECT(J:J) .EQ. ' ') GOTO 42
      IF (SELECT(J:J) .EQ. '.') GOTO 42
41    CONTINUE
      J = 41
42    IF (J .LE .32) GOTO 44
      PRINT (108, 43)
43    FORMAT (' FILENAME TOO LONG')
      GOTO 35
44    IF (J .NE. 1) LMNLIST(I) = SELECT(1:J-1)
      IF (SELECT(J:J) .EQ. '.') ACCTLIST(I) = SELECT(J+1:J+9)
      IF (ACCTLIST(I) .EQ. ' ') ACCTLIST(I) = ':SYS'
50    CONTINUE
      I = SELMAX  + 1
60    ACCTNUM = I - 1
      RETURN
900   INQUIRE (UNIT=1, ERRCODE=ERRNUMBER)
      PRINT (108, 910) ERRNUMBER
      STOP '*THE* *WORD* *I* *WANT* *TO* *GET* *OFF*'
910   FORMAT (' CAN''T OPEN THE FILE: ERROR CODE ',Z4)
      END
      SUBROUTINE TABINIT
      INCLUDE C:LYME
      SAVEFLINK(0) = 0
      SAVEBLINK(0) = 0
      DO 15 I=1,SAVESIZE-1
15    SAVEFLINK(I) = I + 1
      SAVEFLINK(SAVESIZE) = 0
      DO 20 I=1,USERS
           CLOCK(I) = -1
20    CONTINUE
      RETURN
      END
C
C     SUBROUTINE READIN READS THE :LMMON.:SYS FILE AND BUILDS THE
C     TABLES OF LOAD MODULE STATISTICS.
C
      INCLUDE C:LYME
      CHARACTER*(12) PRONAME
      CHARACTER*(8) ACCN
100   FORMAT (4X,A16)
110   FORMAT (//' PROGRAM MONITORING ',A7,' AT ',A16/)
1000  CONTINUE
X     READ (1,1050,END=9000) DATA
1050  FORMAT (11R4)
X     PRINT(108,1060) DATA
1060  FORMAT(/(8(3X,Z8)))
X     BACKSPACE 1
      READ (1,1100,END=9000) USER,BC,PRONAME,ACCN,CPUT,CALCNT,IO,
     + CLOCKT,LINK
1100  FORMAT (R4,R1,A11,A8,5R4)
      EVENTS = EVENTS + 1
      IF (ABS(USER) .NE. 999) GOTO 1900
      BACKSPACE 1
      READ (1, 100) TIME
      IF (USER .LT. 0) GOTO 1200
      IF (STARTIME .EQ. ' ') STARTIME = TIME
      ENDTIME = ' (I''M UNSURE)'
      PRINT (108, 110) 'STARTED', TIME
      CALL TABINIT
      GOTO 1000
1200  ENDTIME = TIME
      PRINT (108, 110) 'STOPPED',TIME
      GOTO 1000
1900  IF (USER) 3000,1000,2000
2000  IF (PRONAME(1:BC) .EQ. 'DELTA') GOTO 1000
      IF (BC .EQ. 3 .AND. PRONAME(3:3) .EQ. 'N' .AND.
     + PRONAME(1:1) .LT. 'a') GOTO 1000
      CALL USERCHK(USER, &1000)
      IF (LINK.EQ.0) GOTO 2120
      I = SAVEFLINK(1)
      IF (I .EQ. 0) GOTO 2120
X     PRINT (108,2054) USER,PROC(USER),ACCT(USER),
X    + CPUT,CALCNT,IO,CLOCKT,LINK
2054  FORMAT ('0USER ',Z2,' PUSH  ',A12,2X,A8,5I9)
      SAVEFLINK(1) = SAVEFLINK(I)
      SAVEPROC(I) = PROC(USER)
      SAVEACCT(I) = ACCT(USER)
      SAVECPU(I) = CPU(USER)
      SAVECLK(I) = CLOCK(USER)
      SAVECAL(I) = CALCOUNT(USER)
      SAVEIO(I) = IOCOUNT(USER)
      SAVEUSER(I) = USER
      SAVEFLINK(0) = I
      SAVEBLINK(I) = 0
      SAVEBLINK(SAVEFLINK(I)) = I
      GOTO 2200
2100  IF (SAVEFLUSH) GOTO 2120
      SAVEFLUSH = .TRUE.
      PRINT (108, 2110)
2110  FORMAT (' SAVED-PROGRAM "STACK" OVERFLOW - RECOMPILE!')
2120  I = SAVEFLINK(0)
      REPEAT 2150, WHILE (I .NE. 0)
      J = SAVEFLINK(I)
      IF (SAVEUSER(I) .EQ. USER) CALL DELINK(I)
2150  I = J
2200  PROC(USER) = PRONAME(1:BC) // BLANKS(1:12-BC)
      ACCT(USER) = ACCN
      CPU(USER) = CPUT
      CALCOUNT(USER) = CALCNT
      IOCOUNT(USER) = IO
      CLOCK(USER) = CLOCKT
X     PRINT (108,2001) USER,PRONAME,ACCN,CPUT,CALCNT,IO,CLOCKT,LINK
2001  FORMAT ('0USER ',Z2,' START ',A12,2X,A8,5I9)
      GOTO 1000
3000  USER = ABS(USER)
      CALL USERCHK(USER, &1000)
      IF (CLOCK(USER) .LT. 0) GOTO 1000
X     PRINT (108,3005) USER,PROC(USER),ACCT(USER),
X    + CPUT,CALCNT,IO,CLOCKT,LINK
3005  FORMAT ('0USER ',Z2,' END   ',A12,2X,A8,5I9)
      DELTACPU = CPUT - CPU(USER)
      DELTAIO = IO - IOCOUNT(USER)
      DELTACAL = CALCNT - CALCOUNT(USER)
      DELTACLK = CLOCKT - CLOCK(USER)
      IF (ACCTNUM .EQ. 0) GOTO 3105
      DO 3101 I=1,ACCTNUM
      IF (LMNLIST(I) .EQ. ' ') GOTO 3070
      IF (LMNLIST(I) .NE. PROC(USER)) GOTO 3101
3070  IF (ACCTLIST(I) .EQ. ACCT(USER)) GOTO 3105
3101  CONTINUE
      GOTO 3300
3105  LOW = 1
      HIGH = NPROCS
      IF (HIGH .EQ. 0) I = 1; GOTO 3180
3106  I = (LOW + HIGH) / 2
      IF (HIGH .LT. 1 .OR. LOW .GT. NPROCS) GOTO 3110
      IF (PROC(USER) .EQ. NAME(I) .AND.
     +    ACCT(USER) .EQ. ACCOUNT(I)) GOTO 3200
      IF (LOW .GE. HIGH) GOTO 3110
      IF (PROC(USER) .GT. NAME(I)) GOTO 3108
      IF (ACCT(USER) .GT. ACCOUNT(I)) GOTO 3108
3107  HIGH = I - 1
      GOTO 3106
3108  LOW = I + 1
      GOTO 3106
3110  IF (NPROCS .LT. PROCS) GOTO 3150
      IF (.NOT. OVERFLOW) PRINT (108,3125)
3125  FORMAT   (' LOAD MODULE TABLE OVERFLOW - RECOMPILE!')
      OVERFLOW = .TRUE.
      GOTO 3300
3150  I = MAX(1, I - 2)
      REPEAT 3160, WHILE (I .LE. NPROCS)
      IF (PROC(USER) .LT. NAME(I) .OR.
     +   (PROC(USER) .EQ. NAME(I) .AND. ACCT(USER) .LT. ACCOUNT(I)))
     +   GOTO 3170
3160  I = I + 1
      I = NPROCS + 1
3170  DO 3180, J = NPROCS, I, -1
      NAME(J + 1) = NAME(J)
      ACCOUNT(J + 1) = ACCOUNT(J)
      INVOKE(J + 1) = INVOKE(J)
      TOTCPU(J + 1) = TOTCPU(J)
      TOTIO(J + 1) = TOTIO(J)
      TOTCAL(J + 1) = TOTCAL(J)
      TOTWALL(J + 1) = TOTWALL(J)
3180  CONTINUE
      NPROCS = NPROCS + 1
C
      NAME(I) = PROC(USER)
      ACCOUNT(I) = ACCT(USER)
      INVOKE(I) = 0
      TOTCPU(I) = 0
      TOTIO(I) = 0
      TOTCAL(I) = 0
      TOTWALL(I) = 0
3200  CALL VERIFY(DELTACPU, 'CPU TIME', USER, &3300)
      CALL VERIFY(DELTAIO, 'I/O COUNT', USER, &3300)
      CALL VERIFY(DELTACAL, 'CAL COUNT', USER, &3300)
      CALL VERIFY(DELTACLK, 'RUN TIME', USER, &3300)
      INVOKE(I) = INVOKE(I) + 1
      TOTCPU(I) = TOTCPU(I) + DELTACPU
      TOTIO(I) = TOTIO(I) + DELTAIO
      TOTCAL(I) = TOTCAL(I) + DELTACAL
      TOTWALL(I) = TOTWALL(I) + DELTACLK
3300  CLOCK(USER) = -1
      I = SAVEBLINK(0)
      REPEAT 3400, WHILE (I .NE. 0)
      IF (SAVEUSER(I) .NE. USER) GOTO 3400
      J = I
      IF (SAVEUSER(J) .NE. USER) GOTO 3350
      SAVECPU(J) = SAVECPU(J) + DELTACPU
      SAVECLK(J) = SAVECLK(J) + DELTACLK
      SAVECAL(J) = SAVECAL(J) + DELTACAL
      SAVEIO(J) = SAVEIO(J) + DELTAIO
3350  J = SAVEBLINK(J)
      PROC(USER) = SAVEPROC(I)
      ACCT(USER) = SAVEACCT(I)
      CPU(USER) = SAVECPU(I)
      CLOCK(USER) = SAVECLK(I)
      CALCOUNT(USER) = SAVECAL(I)
      IOCOUNT(USER) = SAVEIO(I)
      CALL DELINK(I)
X     PRINT (108,3305) USER,PROC(USER),ACCT(USER),CPU(USER),
X    + CALCOUNT(USER),IOCOUNT(USER),CLOCK(USER),LINK
3305  FORMAT ('0USER ',Z2,' PULL  ',A12,2X,A8,5I9)
      GOTO 3500
3400  I = SAVEBLINK(I)
3500  GOTO 1000
      GOTO 1000
9000  CLOSE (1)
      PRINT (108,9100) EVENTS
9100  FORMAT (//' # OF LMMON EVENTS',I10)
      RETURN
      END
      SUBROUTINE VERIFY(VALUE, IDENT, USER, *)
      CHARACTER*(*) IDENT
      INCLUDE C:LYME
      IF (VALUE .GE. 0) RETURN
      PRINT (108, 10) EVENTS, IDENT, PROC(USER), ACCT(USER)
10    FORMAT (' BAD DATA IN FILE: EVENT #',I,'- NEGATIVE ',A,
     + ' ON PROGRAM ',A12,' ',A8)
      RETURN 1
      END
      SUBROUTINE CALC
C
C     SUBROUTINE CALC CALCULATES THE AVERAGE-PER-USE VALUE OF THE
C     LOAD MODULE USAGE STATISTICS.
C
      INCLUDE C:LYME
      DO 10 I=1,NPROCS
      CPUPER(I)  = (TOTCPU(I) + INVOKE(I) / 2) / INVOKE(I)
      CALPER(I)  = (TOTCAL(I) + INVOKE(I) / 2) / INVOKE(I)
      IOPER(I)   = (TOTIO(I) + INVOKE(I) / 2) / INVOKE(I)
      WALLPER(I) = (TOTWALL(I) + INVOKE(I) / 2) / INVOKE(I)
10    CONTINUE
      CALL MAKESEC(TOTCPU)
      CALL MAKESEC(CPUPER)
      CALL MAKESEC(TOTWALL)
      CALL MAKESEC(WALLPER)
      RETURN
      END
      SUBROUTINE CLEANUP
C
C     SUBROUTINE CLEANUP SCROLLS THROUGH THE LOAD-MODULE TABLES
C     AND DELETES THOSE ENTRIES THAT WERE (PROBABLY) DUE TO
C     USERS CALLING NON-EXISTANT LOAD MODULES.  AN ENTRY IS
C     DELETED IF IT REQUIRES LESS THAN ONE SECOND OF CPU
C     TIME PER USE AND ISSUED FOUR CALS PER USE.  CLEANUP
C     ALSO DELETES ANY RECORD FOR LOGON.:SYS AND FOR ANY LOAD
C     MODULE WHOSE NAME CONTAINS ANY UNPRINTABLE CHARACTERS.
C
      INCLUDE C:LYME
      CHARACTER*40 ALPHA
      DATA ALPHA/'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$:# '/
      I = 0
      DO 100 J=1,NPROCS
      IF (INVOKE(J) .EQ. 0) GOTO 100
      IF (NAME(J) .EQ. 'LOGON' .AND. ACCOUNT(J) .EQ. ':SYS') GOTO 100
      DO 5 K=1, 12
      DO 4 L=1, 40
4     IF (NAME(J)(K:K) .EQ. ALPHA(L:L)) GOTO 5
      GOTO 100
5     CONTINUE
      IF (CPUPER(J) .EQ. 0 .AND. CALPER(J) .EQ. 4) GOTO 100
      I = I + 1
      IF (I .EQ. J) GOTO 100
      NAME(I) = NAME(J)
      ACCOUNT(I) = ACCOUNT(J)
      DO 10 K=1, 9
10    MOVE(I, K) = MOVE(J, K)
100   CONTINUE
      NPROCS = I
      RETURN
      END
      SUBROUTINE SORT(KEY)
C
C     SUBROUTINE SORT SORTS THE LOAD MODULE USAGE TABLES INTO
C     ORDER BASED ON ONE OF THE STATISTICS (OR THE LOAD MODULE
C     NAME OR ACCOUNT NUMBER). THE HEAPSORT METHOD IS USED
C     TO SORT THE TABLES.
C
      INCLUDE C:LYME
      LOGICAL ORDERED
      EXTERNAL ORDERED
      DO 100 I=0, NPROCS / 2 - 1
      CALL HEAPIFY(NPROCS / 2 - I, NPROCS, KEY)
100   CONTINUE
      DO 200 I=1, NPROCS - 1
      CALL SWAP(1, NPROCS - I + 1)
200   CONTINUE
      RETURN
      END
      SUBROUTINE HEAPIFY(START, END, KEY)
C
C     SUBROUTINE HEAPIFY MAKES A HEAP OUT OF TABLE
C     ELEMENT 'START' AND ALL ITS CHILDREN.
C
      INCLUDE C:LYME
      LOGICAL ORDERED
      EXTERNAL ORDERED
      I = START
      REPEAT 100, WHILE (I .LE. END / 2)
      J2 = I * 2
      IF (J2 .EQ. END) GOTO 10
      IF (.NOT. ORDERED(J2, J2+1, KEY)) J2 = J2+1
10    IF (ORDERED(I, J2, KEY)) RETURN
      CALL SWAP(I, J2)
      I = J2
100   CONTINUE
      RETURN
      END
      LOGICAL FUNCTION ORDERED(I, J, KEY)
C
C     FUNCTION ORDERED RETURNS A VALUE OF .TRUE. IF
C     ELEMENTS I AND J ARE IN THE CORRECT HEAP ORDER
C     OR .FALSE. IF THEY ARE NOT.
C
      INCLUDE C:LYME
      IF (KEY) 30, 20, 10
10    IF (MOVE(I, KEY) .EQ. MOVE(J, KEY)) GOTO 20
      ORDERED = MOVE(I, KEY) .LT. MOVE(J, KEY)
      RETURN
20    IF (ACCOUNT(I) .EQ. ACCOUNT(J)) GOTO 30
      ORDERED = ACCOUNT(I) .GT. ACCOUNT(J)
      RETURN
30    ORDERED = NAME(I) .GT. NAME(J)
      RETURN
      END
      SUBROUTINE SWAP(I, K)
C
C     SUBROUTINE SWAP EXCHANGES TWO ROWS IN THE LOAD MODULE
C     STATISTICS TABLE (UNLESS THEY'RE THE SAME ROW, IN WHICH CASE
C     IT DOES NOTHING).
C
      INCLUDE C:LYME
      IF (I .EQ. K) RETURN
      DO 110 L=1, 9
      J = MOVE(I, L)
      MOVE(I, L) = MOVE(K, L)
      MOVE(K, L) = J
110   CONTINUE
      CHAR = NAME(I)
      NAME(I) = NAME(K)
      NAME(K) = CHAR
      CHAR = ACCOUNT(I)
      ACCOUNT(I) = ACCOUNT(K)
      ACCOUNT(K) = CHAR
      RETURN
      END
      SUBROUTINE PRINT(KEY, ID)
C
C     SUBROUTINE PRINT CALLS SORT TO PUT THE STATISTICS TABLE IN THE
C     DESIRED ORDER, PRINTS A PAGE HEADING, AND CALLS PRINTAB TO
C     PRINT THE USAGE REPORT.
C
      INCLUDE C:LYME
      CHARACTER ID*(*)
      CALL SORT(KEY)
      PRINT (108, 10) LEN(ID),ID,STARTIME,ENDTIME
10    FORMAT('1'/T10,' ** SORTED BY ',AN,' **',10X,'FROM ',A16,
     + ' TO ',A16)
      CALL PRINTAB
      RETURN
      END
      SUBROUTINE PRINTAB
C
C     SUBROUTINE PRINTAB PRINTS COLUMN HEADINGS FOR THE USAGE REPORT,
C     AND THEN FORMATS AND PRINTS THE REPORT.
C
      INCLUDE C:LYME
      EXTERNAL HMS
      CHARACTER*9 HMS
10    FORMAT (A1,'LOAD MODULE',T16,'ACCOUNT',T27,'# OF',T36,' TOTAL ',
     + T47,'CPU TIME',T61,'TOTAL #',T70,'   CALS',T82,'TOTAL #',T91,
     + '    I/O',T101,' TOTAL ',T112,'RUN TIME'/
     + ' NAME',T16,'NUMBER',T27,'USES',T36,'CPU TIME',
     + T47,' PER USE',T61,'OF CALS',T70,'PER USE',T82,' OF I/O',T91,
     + 'PER USE',T101,'RUN TIME',T112,' PER USE'/)
      DO 100 I=1,NPROCS
      IF (I .EQ. 1) GOTO 40
      IF (MOD(I, LPP) .NE. LPP - 2) GOTO 50
      PRINT (108, 10) '1'
      GOTO 50
40    PRINT (108, 10) '0'
50    PRINT (108,110) NAME(I), ACCOUNT(I), INVOKE(I),
     + HMS(TOTCPU(I)), HMS(CPUPER(I)),
     + TOTCAL(I), CALPER(I),
     + TOTIO(I),  IOPER(I),
     + HMS(TOTWALL(I)), HMS(WALLPER(I))
100   CONTINUE
110   FORMAT(' ',A12,2X,A8,3X,I4,4X,2(A9,2X),2X,2I9,3X,2I9,2X,2(A9,2X))
      RETURN
      END
      SUBROUTINE REPORT
C
C     SUBROUTINE REPORT CALLS SORT TO PRESORT THE STATISTICS TABLE
C     REPEATEDLY TO GENERATE THE NINE SORTED USAGE REPORTS.
C
      CALL SORT(0)
      CALL PRINT(-1, 'LOAD MODULE NAME')
      IF (ACCTNUM .NE. 1) CALL PRINT(0, 'LOAD MODULE ACCOUNT NUMBER')
      CALL PRINT(1,'# OF USES')
      CALL PRINT(2, 'TOTAL CPU TIME')
      CALL PRINT(6, 'CPU TIME / USE')
      CALL PRINT (3, 'TOTAL # OF CALS')
      CALL PRINT (7, '# OF CALS / USE')
      CALL PRINT (4, 'TOTAL # OF I/O ACCESSES')
      CALL PRINT (8, '# OF I/O ACCESSES / USE')
      CALL PRINT (5, 'TOTAL WALL-CLOCK TIME')
      CALL PRINT (9, 'WALL CLOCK TIME / USE')
      RETURN
      END
      SUBROUTINE MAKESEC(VECTOR)
      INCLUDE C:LYME
      INTEGER VECTOR(PROCS)
      DO 10 I=1,NPROCS
10    VECTOR(I) = (VECTOR(I) + 250) / 500
      RETURN
      END
      CHARACTER*9 FUNCTION HMS (SECONDS)
C
C     FUNCTION HMS ACCEPTS AN INTEGER ARGUMENT AND TREATS IT AS
C     A COUNT OF SECONDS;  IT RETURNS A 9
C     CHARACTER STRING OF THE FORM HHH:MM:SS.
C
      CHARACTER DIGITS*10,STRING*9
      IMPLICIT INTEGER (A-Z)
      DATA DIGITS/'0123456789'/
      EXTERNAL CODA
      CHARACTER*2 CODA
      SEC = SECONDS
      HRS = SEC / 3600
      SEC = MOD(SEC, 3600)
      MIN = SEC / 60
      SEC = MOD(SEC, 60)
      HUNDREDS = MOD(HRS / 100, 10) + 1
      STRING = DIGITS(HUNDREDS:HUNDREDS) // CODA(HRS) // ':' //
     + CODA(MIN) // ':' // CODA(SEC)
      DO 10 I=1,6
      IF (STRING(I:I) .NE. '0' .AND. STRING(I:I) .NE. ':') GOTO 20
10    STRING(I:I) = ' '
20    HMS = STRING
      RETURN
      END
      CHARACTER*2  FUNCTION CODA(ITEM)
C
C     AND CONVERTS IT TO A 2-CHARACTER STRING WHICH IT RETURNS TO
C     THE CALLER.  NO ZERO SUPPRESSION;  OVERFLOW DIGITS ARE
C     TRUNCATED.
C
      CHARACTER DIGITS*10
      IMPLICIT INTEGER (A-Z)
      DATA DIGITS/'0123456789'/
      HIGH = MOD(ITEM / 10, 10) + 1
      LOW = MOD(ITEM, 10) + 1
      CODA = DIGITS(HIGH:HIGH) // DIGITS(LOW:LOW)
      RETURN
      END
      SUBROUTINE DELINK(ID)
C
C     SUBROUTINE DELINK ACCEPTS AN ARGUMENT WHOSE VALUE IS THE INDEX
C     NUMBER OF AN ENTRY IN THE 'SAVED PROGRAM' TABLES;  IT DECHAINS
C     THE ENTRY, PLACES IT ON THE FREE-ENTRY LIST, AND RETURNS.
C
      INCLUDE C:LYME
      SAVEFLINK(SAVEBLINK(ID)) = SAVEFLINK(ID)
      SAVEBLINK(SAVEFLINK(ID)) = SAVEBLINK(ID)
      SAVEFLINK(ID) = SAVEFLINK(1)
      SAVEFLINK(1) = ID
      RETURN
      END
      SUBROUTINE USERCHK(ID, *)
C
C     SUBROUTINE USERCHK ACCEPTS A USER NUMBER AS AN ARGUMENT.  IF THE
C     USER NUMBER IS TOO LARGE FOR THE USER'S INFORMATION TO BE
C     PLACED IN THE CURRENT-USAGE TABLES, AN ERROR MESSAGE IS PRINTED
C     AND USERCHK DOES A NON-STANDARD RETURN TO THE STATEMENT PASSED
C     AS THE SECOND ARGUMENT.  IF THE USER NUMBER IS OK, USERCHK
C     SIMPLY RETURNS.
C
      INCLUDE C:LYME
      IF (ID .GE. 256) GOTO 20
      IF (ID .LE. USERS) RETURN
      IF (USERBIG) RETURN 1
      PRINT (108, 10) ID
10    FORMAT (' USER TABLE OVERFLOW ON USER ',Z,'  - RECOMPILE!')
      USERBIG = .TRUE.
      RETURN 1
20    PRINT (108, 30) EVENTS, ID
30    FORMAT (' BAD DATA IN FILE: EVENT #',I,'- USER # ',Z)
      RETURN 1
      END
      BLOCK DATA

      DATA OVERFLOW /.FALSE./
      DATA SAVEFLUSH /.FALSE./
      DATA USERBIG /.FALSE./
      DATA BLANKS/'            '/
      DATA EVENTS /0/
      DATA STARTIME/' '/
      END

