But it's quite permissible to do your own indenting, as this sample code, courtesy of Erich Staubringer, shows:
*add sgs [*file]./toc *clear found *set stmt$ = 0 *increment i to 1 * if column search from toc,stmt$+1,6 for [*element] * if [toc,stmt$,4,1] = SYM * set found * else * clear i * endif * endif *loopNotice that the directives still begin in column 1 but there are spaces between the asterisk and the remainder of the directive. This is quite legal. And it's legal in ECL to leave spaces between the masterspace (@) and the command or processor name.
I think the decision of whether to indent or not is a personal one.
*SET EQUIPMENT = '[*PACK,16,2]' *SET EQUIPMENT = '[*PACK,LSTR$,2]' *SET EQUIPMENT = '[*PACK,LEFT_STRING$,2]'
Please, please, please, don't answer "The first one." Skeletons that use numeric references are much harder to understand than those that use symbolic ones. I once came across a statement like this a couple of years after I wrote it:
*IF COLUMN SEARCH FROM PACK,1,2,1 FOR [TIPFILE,T,4,1]
I hadn't the faintest idea what this test meant. I had to look at the PACK and TIPFILE SGSs to try to decipher the intent. My task would have been easier had the statement looked like this:
*IF COLUMN SEARCH FROM PACK,1,PACK_PREP_FACTOR,1 FOR [TIPFILE,T,TIPFILE_REC_SIZE,1]
I find that the use of symbolic references within square brackets is the single most important factor in writing clear SSG skeletons. It's the equivalent of using EQUs and EQUFs in MASM.
The problem with setting the A option is that SSG won't tell you about bad no-finds. These are no-finds resulting from typing errors or other programming mistakes: The A option will force the skeleton will continue and the error will be obscured. I want all the help I can get in isolating my errors as early as possible.
Unless its meaning is self-evident, include an in-line comment describing the condition
intended by the *IF or *ELSEIF directives. Example:
*IF [SUPS$,1,6,1] = 000000000000 . If no Core Block SUPS (i.e., if XPA system)
*INCREMENT R TO [REEL,1,1] . For each input reel number
*CLEAR LINE_CNT . Counts # of lines printed on current page *CLEAR TOT_LINE_CNT . Counts # of lines printed on all pages
*IF FIRST_NAME = 'STEVE'
This compares two constants and so can never be true. It's wrong on two counts. First, if FIRST_NAME is a string variable, the value-of construct ([*var]) must be used in *IF:
*IF [*FIRST_NAME] = 'STEVE'
Second, a string literal in a *IF must not be placed in single quotes. (But in *SET directives string literals are placed in single quotes. This is one of those inconsistencies that vex beginners.) Thus, our corrected statement is:
*IF [*FIRST_NAME] = STEVE
You can also put a string literal in two single quotes. Sometimes you'll need to do so because the string will contain a special character that can fool SSG:
*IF [*FIRST_NAME] = ''Mary-Jo''
In an integer context, use a plus sign (+) to indicate that it's an integer variable rather than a string literal: :
*IF +DELETE_CNT > 100
And be careful when including an integer expression to include a plus sign (+) in the first token of the expression. Compare
*IF +DELETE_CNT > MAX+3 . syntax OKwith
*IF +DELETE_CNT > MAX + 3 . syntax error!
It shouldn't work this way, but it does!
*INCREMENT U TO [USERID] *SET FIRST_NAME = '[USERID,[*U],4,2]' ... *LOOP
Within an SGS reference the occurrence, field and subfield must be integers. The variable U above is an integer but the programmer unnecessarily uses value-of. This causes SSG to convert U to a string and then back to an integer, thereby harming efficiency and, more important, readability. A more straightforward *SET directive is:
*SET FIRST_NAME = '[USERID,U,4,2]'
Similarly, when doing arithmetic
*SET FILE_CNT = FILE_CNT + 1
is preferable to
*SET FILE_CNT = [*FILE_CNT] + 1
*SET USERID_DELETE_CNT = 7 *SET USERID_DELETE_CNT_YTD = 20 *DISPLAY '[*USERID_DELETE_CNT]' *DISPLAY '[*USERID_DELETE_CNT_YTD]'
What values will be displayed? Both *DISPLAYs will display 20. This is because while SSG allows variable names to be up to 30 characters long, it uses only the first 16 characters internally. Thus, SSG sees only one variable here while the programmer sees two. This is the kind of help you don't need from a language processor! I suggest restricting your variable names to 16 characters.
*INCREMENT U TO [USERID] . For each user-id *INCREMENT V TO [VIOLATION] . For each security violation ... *DISPLAY 'User [USERID,U,1,1] committed a [VIOLATION,V,1,1]' *LOOP . V *LOOP . UThis is merely a convention that I find to be intuitive. Another appraoch that would be quite intuitive, although somewhat verbose, is:
*INCREMENT USERID_NDX TO [USERID] . For each user-id *INCREMENT VIOLATION_NDX TO [VIOLATION] . For each security violation ... *DISPLAY 'User [USERID,USERID_NDX,1,1] committed a [VIOLATION,VIOLATION_NDX,1,1]' *LOOP . VIOLATION_NDX *LOOP . USERID_NDX
Here the loop index when scanning a set of SGSs is the SGS label with '_NDX' appended.
|Type of variable||Suffix||Example||Counter||
*SET X = 7 *. *INCREMENT X FROM 0 TO 9 BY 1 *CREATE SGS: DIGIT [*X] *LOOP *. *DISPLAY '[*X]'
What value of X will be displayed? The answer is 7. The reason for this is because the X referred to in the *SET and *DISPLAY directives is a different X than the one referred to in the *INCREMENT and *CREATE directives. The former is a global variable whereas the latter is a local variable, A *INCREMENT or *DO automatically creates a local variables that survives for the life of the loop.
This is SSG's sole attempt to provide scope for variables. Because it can lead to confusion, I believe it does more harm than good. I suggest never taking advantage of this feature--it's apt to confuse anyone who reads your skeletons.
*SET WRDS__PER__TRK = 1792 . constant
*REMOVE VARIABLE WRDS__PER__TRK . not to be used outside this proc
*INCREMENT U TO [USERID] ... *LOOP U
Strictly speaking the *LOOP directive above is syntactically incorect. The SSG Programmer Reference Manual does not specify that *LOOP can take a variable name. But SSG does not flag this as a syntax violation because as soon as it sees '*LOOP ' it terminates scanning that line. There is some danger that a future release of SSG will implement a stricter syntax check and flag statements like this in error (although this change would break a million skeletons and so Unisys should think twice).
A syntactically correct way to indicate to the reader which *INCREMENT the *LOOP belongs to is to make use an in-line comment on the *LOOP:
*INCREMENT U TO [USERID] ... *LOOP . U
*ACCEPT USERID_IN 'Please enter user-id' *SET USERID = '[*USERID_IN,UCSTR$]' . translate to upper case *REMOVE VARIABLE USERID_IN . no longer needed
This saves the demand user much frustration at the cost of very little code.
*ACCEPT PACKID 'Please enter pack-id' *CLEAR PACK_OK_FLG . assume pack-id is invalid *. *INCREMENT P TO [PACK] . for each pack *IF [PACK,P,1,1] = [*PACKID,UCSTR$] . if pack-id is what user specified *SET PACK_OK_FLAG . indicate that we found it *EXIT P . no need to look further *ENDIF *LOOP *. *IF PACK_OK_FLAG IS SET . if user made good choice *RETURN . continue *ELSE *DISPLAY 'Error: invalid pack-id' *ENDIF
Or you could code:
*ACCEPT PACKID 'Please enter pack-id' *IF COLUMN SEARCH FROM PACK,1,1,1 FOR [*PACKID,UCSTR$] . if pack-id is valid *RETURN . continue *ELSE *DISPLAY 'Error: invalid pack-id' *ENDIF
With *IF COLUMN SEARCH your code is simpler and faster. In this case, we avoided a loop and a flag variable.
I find that if I've got 1000 or more SGSs to sort performance may be unacceptably slow. And it seems to scale exponentially, although I haven't run any careful tests. If you've got 50,000 SGSs to sort, forget it.
The alternative is to write the SGSs to a file, use @SORT (or the @ZIP SORT command which calls Sort/Merge), and then re-invoke SSG to process the sorted SGSs. I've cut large sorts from minutes to seconds by doing this.
*DISPLAY 'This message lacks a closing quote
In cases like this, a job with a syntactically incorrect skeleton (for which SSG will not @ADD any generated ECL) can FIN normally. Sometimes the syntax error won't be apparent because it'll be buried in a rarely-used path within the code. SSG does increment ERRCNT$, however, on any syntax error. Thus, you can catch errors by executing this as the last code before skeleton termination:
*IF ERRCNT$ IS SET AND [INFO$,1,5,1] <> DEMAND *DISPLAY 'Fatal: ERRCNT$ has been set during skeleton processing' *ABORT . or *MESSAGE,X if you want ER ERR$ *ENDIF
I use this so frequently that I've created a *COPY proc for it. I suggest putting it in all important batch skeletons.