Unfortunately, for releases below 24R3 there is no systematic way to tell from within a skeleton what SSG features are available. However, because SSG has added system-defined SGSs (or new fields on previously existing SGSs) or system-defined variables over the years, you can often use these to determine what older release of SSG is in use:
*IF +FEATURE_LEVEL$ = 1 . if SSG 24R3 * DISPLAY '24R3' *. *ELSEIF [SYSTEM$,1,3,2] . if SSG 24R2 or higher * DISPLAY '24R2 or higher' *. *ELSEIF NEW_BOOLEAN$ IS CLEAR OR NEW_BOOLEAN$ IS SET . if SSG 24R1 or higher * DISPLAY '24R1 or higher' *. *ELSEIF [SYSTEM$,1,4,1] . if SSG 23R8 or higher * DISPLAY '23R8 or higher' *. *ELSEIF [INFO$,1,4,2] . if SSG 23R7 or higher * DISPLAY '23R7 or higher' *. *ELSEIF [SYSTEM$,1,2,2] . if SSG 23R6 or higher * DISPLAY '23R6 or higher' *. *ELSEIF [INFO$,1,7,1] . if SSG 23R5 or higher * DISPLAY '23R5 or higher' *. *ELSEIF [INFO$,1,3,3] . if SSG 23R3 or higher * DISPLAY '23R3 or higher' *. *ELSEIF [SUPS$,1,7,1] . if SSG 23R2 or higher * DISPLAY '23R2 or higher' *. *ELSEIF [DATE$] . if SSG 23R1 or higher * DISPLAY '23R1 or higher' *. *ELSE * DISPLAY 'Lower than 23R1' *. *ENDIF
*IF [SUPS$,1,6,1] = 000000000000 . If no Core Block SUPS (i.e., if XPA system) *. Do exciting M-Series things *ELSE *. Do boring C-series things *ENDIF
To correct these three faults, I wrote my own version of this proc. It provides the day of the week for any eight-digit input date (format YYYYMMDD) between 1964 and 2099 or for any six-digit input date (format YYMMDD) between 1964 and 2027 (it uses the Unisys Standard of 2027 to convert YYMMDD to YYYYMMDD).
Unisys has corrected the year 2000 bug in their proc beginning with the SSG 23R2 release. But the other two problems remain, which is fair because it's only demonstration code.
*CLEAR JAN_CNT . counts security violations for January *CLEAR FEB_CNT . counts security violations for February ... (nine *CLEAR directives omitted here) *CLEAR DEC_CNT . counts security violations for December *. *INCREMENT V TO [VIOLATION] . for each security violation *IF [VIOLATION,V,V_DATE,1,SUBSTR$,5,2] = 01 . if violation occurred in January *SET JAN_CNT = JAN_CNT + 1 *ELSEIF [VIOLATION,V,V_DATE,1,SUBSTR$,5,2] = 02 . if violation occurred in February *SET FEB_CNT = FEB_CNT + 1 ... (ten *ELSEIF and *SET directives omitted here) *ENDIF *LOOPYou'll wind up with one mother of a *IF statement. A more compact method is to fake an array by adjusting the variable name based on the month number
*INCREMENT M TO 12 . there are 12 months *CLEAR MONTH_CNT_[*M] ... . counts security violations for month M *LOOP *. *INCREMENT V TO [VIOLATION] . for each security violation *SET MX = +[VIOLATION,V,V_DATE,1,SUBSTR$,5,2] . convert month to integer (no leading zero) *SET MONTH_CNT_[*MX] = MONTH_CNT_[*MX] + 1 *LOOP
*REMOVE SGS BADUSER,1,[BADUSER] . just to be safe! *. *INCREMENT V TO [VIOLATION] . for each security violation *IF COLUMN SEARCH FROM BADUSER,1,1,1 FOR [VIOLATION,V,V_USERID,1] . if not first for user *SET TOT = [BADUSER,STMT$,2,1] + 1 . increment the user's violation count *REMOVE SGS BADUSER,STMT$,1 . remove the user's SGS *ELSE *SET TOT = 1 . first violation for this user *ENDIF *CREATE SGS: BADUSER [VIOLATION,V,V_USERID,1] [*TOT] *LOOP
This code will create one BADUSER SGS for each user who committed a security violation. The first field will contain the user-id; the second field will contain the number of security violations committed by that user.
I find this method to be more flexible and straighforward than the other method.
*DISPLAY ' User-id Count' . report heading *. *INCREMENT B TO [BADUSER] . for each user who commited a violation *DISPLAY '[BADUSER,B,1,1] [BADUSER,B,2,1]' *LOOP
This will get the job done but will look ugly, for two reasons. First, unless all user-ids are the same length, the second (security violation count) field will start in different columns on different lines, leading to a ragged look. We can fix this very simply:
*DISPLAY ' User-id Count' . report heading *. *INCREMENT B TO [BADUSER] . for each user who commited a violation *DISPLAY '[BADUSER,B,1,1,LSTR$,12] [BADUSER,B,2,1]' *LOOP
The maximum length of the user-id field is 12 characters. The LSTR$ (left-string) reference will force SSG to display all occurrences of this field as 12 characters (it will pad with trailing spaces where necessary). This removes the ragged look from the report.
But the second problem with this report remains: The count field will be left-justified and it's more traditional to right-justify numeric fields. We can do this by:
*DISPLAY ' User-id Count' . report heading *SET BLANKS = ' ' . constant *. *INCREMENT B TO [BADUSER] . for each user who commited a violation *SET OUT_CNT = '[*BLANKS][BADUSER,B,2,1]' . pad the count with leading blanks *DISPLAY '[BADUSER,B,1,1,LSTR$,12] [*OUT_CNT,RSTR$,8]' *LOOP
I think Arnold Cameron first showed me this trick. RSTR$ (right-string) does not pad with leading spaces if the field has fewer characters than are specified. So we must first pad the number with at least n leading spaces and then to use RSTR$ to select the right-most n characters (where n is the maximum number of digits necessary to show the variable's greatest possible value). In the above example, I was very paranoid and chose eight digits.
*IF [INFO$,1,5,1] = DEMAND . if demand *ACCEPT USERID 'Please enter user-id' *ELSE . batch or deadline batch *ACCEPT,O USERID 'Please enter user-id' *ENDIF
This will lead to overly long skeletons and to maintenance headaches (you'll need to change two messages rather than one). A better method is to set a variable with the *ACCEPT/*DISPLAY option at the beginning of skeleton execution
*IF [INFO$,1,5,1] = DEMAND *SET DEST = 'Z' . Dummy value *ELSE *SET DEST = 'O' . Console operator *ENDIF
In this case I've chosen the variable name DEST (short for destination) but any fairly short variable name will do. I use this so often that I wrote a *COPY proc for it. Then later in the skeleton you can code, for example:
*ACCEPT,[*DEST] USERID 'Please enter user-id'
Strictly speaking, this method is probably syntactically illegal: SSG does not provide a *ACCEPT/*DISPLAY option letter to specify that the input and output uses CARD$ and PRINT$. Instead, the lack of an option letter indicates this. So '*ACCEPT,Z' is not strictly legal. But if a new release ever flagged this as an error it'd break a million skeletons and Unisys would have to back off. This technique is far too valuable to worry about strict syntactical legality. (Unisys could make this legal by choosing an option letter and reserving the letter for this purpose. No code change would be necessary, only a manual update.)
*INCREMENT P TO [PACK] . for each pack *IF [PACK,P,4,1] = FMD . if it's equipment type FMD *REMOVE SGS PACK,P,1 . get rid of it *ENDIF *LOOP
Alas, this fails because the value of loop index (variable P) is not adjusted when a *REMOVE is done. If, for example, you *REMOVE the 7th occurrence, the 8th occurrence would now become the 7th. But the next time thru the loop P would be 8, so you'd miss testing the SGS that follows one you *REMOVE. You could code the adjustment to the index but it'd be tricky.
A better approach is to process the SGSs in reverse order:
*INCREMENT P FROM [PACK] TO 1 BY -1 . for each pack in reverse order *IF [PACK,P,4,1] = FMD . if it's equipment type FMD *REMOVE SGS PACK,P,1 . get rid of it *ENDIF *LOOP
This always works because the occurrences that are being adjusted in response to *REMOVE are in the SGSs that we've already tested.