F$ForgottenAvoidedOverlooked
I have looked at vast quantities of DCL code over the years. Most of the DCL procedures I have seen were written for simple tasks or for one time use. I have also encountered many procedures — some on the order of a thousand lines — that have been written for production and used heavily throughout the production cycle. What I find to be the most amazing about this latter case is that this DCL code is usually some of the worst written, and the least or most poorly documented DCL. However, there is one common denominator in either of these cases and that is the forgotten, avoided, and overlooked lexical function,F$FAO
.
The FAO in
F$FAO
does not stand for Forgotten, Avoided or Overlooked; albeit, from the state of some of the DCL code I have seen in the field, it sure seem that it does. In actuality, it stand for Formatted ASCII Output. This lexical is a DCL adjunct to the OpenVMS system service, SYS$FAO
. This lexical allows the DCL programmer to easily format data but it also provides a gateway to manipulate data for other DCL operations or lexical functions. If you write DCL procedures and you are not intimately aware of the F$FAO
lexical function and its uses, pay careful attention to the subsequent discussion, gripes and peeves.Singular and Plural
When usingF$FAO
to output units associated with a numeric value, one can make the units reflect singular or plural context. Suppose you wanted to output, for example: 1 Block, 16 Blocks. This is accomplish by using the !%S
formatter code. So, if the example just given was coded, it would be: $ WRITE SYS$OUTPUT F$fao("!UL Block!%S",BLOCK_COUNT)
Simple! However, what do you do when there is something that does not follow the typical "add S" plurality rule? Thankfully, there is a formatter specifier for that too.! If you had to output, for example, 1 Mouse, 16 Mice, you could use the
!n%C !%E !%F
formatter codes. The 'n' is the value of the most recently evaluated argument that would need the unique output value. Assuming the rodent example, the 'n' would be 1 and the unique output value would be "Mouse." Using this example, the code would be: $ WRITE SYS$OUTPUT F$fao("!UL !1%CMouse!%EMice!%F",MOUSE_COUNT)
I am always amused ‐ more aptly, dismayed — when I see volumes of DCL
IF THEN ELSE
statements devoted to the output of plural specifiers when there is such a simple mechanism available.Tabularization
Throughout our lives we see data displayed in a table (tabular) format. Grouping like data in a column makes it much easier to read and digest. This is often done with DCL procedures but I see much too much DCL being devoted to tabularizing the data. For example, I recently, at a client site, I came across simple procedure to output the free blocks of a disk. The code which obtained and formatted the free blocks looked something like the following DCL code.
$ BLANKS:=" "
$ LOOP:
$ DEV=F$device("*","DISK")
$ IF DEV.EQS."" THEN $ EXIT
$ WRITE SYS$OUTPUT "''DEV'''F$extract(0,16-F$length(DEV),BLANKS)' "+-
"''F$extract(1,10-F$length(F$getdvi(DEV,"FREEBLOCKS")),BLANKS)'"+-
"''F$getdvi(DEV,"FREEBLOCKS")'"
$ GOTO LOOP
While the above accomplishes the task, it is ugly with too many nested lexical functions and way too many apostrophes. This can be easily reduced to a much more readable form by taking advantage of the format specifiers of the
F$FAO
lexical function. The same output as provided by the above DCL code can be realized with the following, much simplified, DCL code.
$ LOOP: DEV=F$device("*","DISK")
$ IF DEV.EQS."" THEN $ EXIT
$ WRITE SYS$OUTPUT F$fao("!16AS !10UL",DEV,F$getdvi(DEV,"FREEBLOCKS"))
$ GOTO LOOP
Fewer nested lexicals, and less wear and tear on the apostrophe key! The one thing I, personally, do not like is vast amounts of whitespace in output such as this example provides. I like to fill the whitespace with "dots," like a run-on ellipsis, to maintain a progression from the leftmost column of data to the rightmost. This too can be realized with simple
F$FAO
formatters. The following shows the same example but with "dots" filling the excess whitespace in the output.
$ LOOP: DEV=F$device("*","DISK")
$ IF DEV.EQS."" THEN $ EXIT
$ WRITE SYS$OUTPUT F$fao("!16!#*. !UL",DEV,-
10-F$length(F$getdvi(DEV,"FREEBLOCKS")),F$getdvi(DEV,"FREEBLOCKS"))
$ GOTO LOOP
Binary Bewilderment
TheF$FAO
lexical function is a powerful feature when outputting data to a display or a file; however, its usefulness goes beyond just formatting of data for output. One of the things that the F$FAO
lexical function has been used for is to allow access to binary data. For example, most of the OpenVMS utilities which maintain and manipulate time do not store the time as the familiar OpenVMS Date(time) string. Instead, these utilities store the date and time as the OpenVMS time quadword — a value which represents the number of 100 nanosecond intervals since 17-NOV-1858. Use of the F$FAO
lexical function has been used to facilitate access to such is OpenVMS time quadwords. However, its use is not limited to just OpenVMS time quadwords; it can be used to access any binary data. The problem, as I see it, seems to be that too many people have just cut-and-pasted the F$FAO
lexical function syntax to extract binary data without having an understanding of how it works. Here's a simple example which will be examined to help expound on the understanding of how and why this works.
$ DATA:="UUUU"
$ WRITE SYS$OUTPUT F$fao("!@XL",F$cvui(32,32,F$fao("!AD",8,DATA)))
When the above DCL code is executed, the value "55555555" is output. Hexadecimal 55 is the ASCII value for the "U" character. This code is accessing the string symbol,
DATA
, as binary data to output its hexadecimal equivalent. This access is facilitated by the use of the !AD
format directive in the innermost F$FAO
; a directive designed to format a string of a specified length. This directive required two arguments. The first argument is the length of the string to be formatted; the second argument is the address of the string data. However, in DCL, the string symbol reference will pass along the address of a string descriptor; not the address to the string's data. Therefore, the !AD
directive will cause F$FAO
to output the eight byte (two longword) string descriptor — four bytes (one longword) of length (DSC$W_LENGTH, DSC$B_DTYPE, DSC$B_CLASS) and four bytes (one longword) of string address (DSC$A_POINTER).The example then uses the
F$CVUI
lexical function to extract, from the returned descriptor, its 32 bit (one longword) address stored 32 bits (one longword) into the data (ie. DSC$A_POINTER). The !@XL
directive tells the outermost F$FAO
to format, as a hexadecimal longword, the data at this address. This can be seen using the following code:
$ B:="UUUU"
$ FAO:= WRITE SYS$OUTPUT F$fao
$ FAO ("Addr: !XL Content: !-!@XQ",F$fao("!AD",8,B))
$ FAO (" Content: !XL",F$cvui( 0,32,F$fao("!AD",8,B)))
$ FAO (" Content: !XL",F$cvui(32,32,F$fao("!AD",8,B)))
$ FAO ("Addr: !XL Content: !-!@XL",F$cvui(32,32,F$fao("!AD",8,B)))
Which produces the following:
Addr: 7FF9DA0C Content: 7FF9D9D400000008
Content: 00000004
Content: 7FF9D2F4
Addr: 7FF9D2F4 Content: 55555555
Cut and Baste
I was recent asked to look over some DCL which was written to compare, format and output some date strings. The author was quite perplexed when each of the dates being examined printed out as the same date. Here is a simple bit of code which demonstrates the observed problem.
$ DATE_A[ 0,32]=%xC4A84000
$ DATE_A[32,32]=%x00ABABA0
$ DATE_B[ 0,32]=%xEF120000
$ DATE_B[32,32]=%x00ABAC69
$ WRITE SYS$OUTPUT F$fao("A: !%D",F$cvui(32,32,F$fao("!AD",8,DATE_A)))
$ WRITE SYS$OUTPUT F$fao("B: !%D",F$cvui(32,32,F$fao("!AD",8,DATE_B)))
$ WRITE SYS$OUTPUT F$fao("A: !%D!/B: !%D",-
F$cvui(32,32,F$fao("!AD",8,DATE_A)),-
F$cvui(32,32,F$fao("!AD",8,DATE_B)))
The output from this procedure is:
A: 1-JAN-2012 00:00:00.00
B: 2-JAN-2012 00:00:00.00
A: 2-JAN-2012 00:00:00.00
B: 2-JAN-2012 00:00:00.00
What is happening is that DCL uses the same internal work buffer space for evaluating the inner nested
F$FAO
lexicals. Thus, when the two different dates are evaluated in the last line — and they are both evaluated — the latter F$FAO
evaluation overwrites the first F$FAO
evaluation. So what has happened is that this particular DCL coder had fallen victim of cutting and pasting a technique to output binary data without fully understanding how and or why it worked. The solution to this problem was quite simple: evaluate both time values with separate F$FAO
lexical functions and store each result in a separate DCL symbol.