Individual Entry

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 using F$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

The F$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.

One or more comments are waiting for approval by an editor.



Comments?


To thwart automated comment SPAM, you must answer this question to post.

Comment moderation is enabled. Your comment(s) will not be visisble until approved.
Remember personal info?
Notify?
Hide email?
All html tags, with the exception of <b> and <i>, will be removed from your comment. You can make links by simply typing the url or email-address.
Powered by…