Program FDUMP is useful for viewing ASCII files. But because it doesn't show the value of a control character, it is no help in tracking down spurious bytes in an ASCII file, or in examining a file that contains binary data. In this chapter we enhance its capabilities to produce two better file dump commands, CDUMP and VDUMP.
The major shortcoming of FDUMP is that it doesn't show you the value of a control character. Program CDUMP will do so. The ASCII character set is designed so that all but two control characters are paired with uppercase letters. A simple way of naming a control character is to name its matching letter, adding a distinguishing mark of some kind. CP/M uses this convention; it displays control characters as letters preceded by an up-arrow or circumflex, thus: ^C.
The NUL character corresponds to the @ character, even though most keyboards will not generate any output when control-@ is pressed. The DEL control character has no paired letter, so I arbitrarily chose to display the tilde (~) instead.
Most terminals are capable of displaying characters in reverse video, that is, as a black letter on a white background. CDUMP will display a control character as an uppercase letter in reverse video.
CDUMP results from a small modification to FDUMP. In fact, the only real change occurs in the Output subroutine. It must condition the terminal for normal video before writing a normal character, and set it for reverse video before writing a control letter. The translation of control characters has to be changed so that a control character is converted to its paired letter.
For the majority of terminals, reverse video is set on and off by sending one or two control characters to the terminal. Once reverse video has been set on, it stays on until it is set off again. What Output must do, then, is to remember what state it left the terminal in after the last character. If the present character uses that color, fine. If not, control characters must be sent to switch the terminal to the other mode. The terminal should not be switched needlessly; if it is already in the correct mode when a character is to be displayed, the program should not send the reverse sequence again.
These considerations led me to the pseudo-code shown in Figure 2-1, from which I have omitted most of the parts that are identical to the FDUMP pseudo-code in Chapter 1. There will be two constants, NormalSeq and ReversSeq, which contain the sequences of control characters needed to set normal video and reverse video respectively. A switch—with the unimaginative name of Switch—records which sequence was sent last. The terminal is presumably in normal video mode when the program is entered, so Switch must be initialized to UsedNormal at the beginning of the program. Two subroutines, GoToNormal and GoToReverse, encapsulate the logic of switching modes.
The first time I ran CDUMP, a control character occurred at the end of the first line. That put the terminal in reverse video, and there it stayed all through the record number. That was when I inserted the call to GoToNormal in subroutine ShowRecord. The second time I ran CDUMP, the last byte of the file was a control character. When the program ended, the CP/M prompt came out in reverse video. That was when the call to GoToNormal was added to the end of the main loop.
Figure 2-1. The pseudo-code of CDUMP, showing only the changes from FDUMP.
program CDUMP (fileref) ... NormalSeq, ReversSeq are constant strings of bytes; Switch is a byte saying which we used last, it has settings of UsedNormal, UsedRevers. main: ... initialization as for FDUMP. initialize Switch Switch := UsedNormal C := Getchar while( not EOF ): ...main loop is just like FDUMP. end while. GoToNormal { leave terminal in normal video } end main. Getchar : ...as for FDUMP end GetChar. Output( X: a byte ) turn off high bit of X if (X is printable) then GoToNormal else translate X for display GoToReverse endif. TypeChar(X) end Output. GoToNormal: if we aren't in normal video, get there. GoToReverse: if we aren't in reverse video, get there. ShowRecord: GoToNormal { record number in normal video } ...continue as for FDUMP end CDUMP.
The assembler source of CDUMP appears in Listing 2-1. It is very much like the code of FDUMP. There is one coding trick used. The two subroutines GoToNormal and GoToReverse were almost identical. The only difference between them was their choice of the Switch value to test and the control sequence to send. I folded them together so that they have a common conclusion. That made them into a sort of siamese-twin subroutine with two heads and a single body. Like most such optimizations, it saved a few bytes of code at the price of making the program slightly more obscure.
CDUMP has a hardware dependency. The constants NormalSeq and ReverseSeq have to be customized for the kind of terminal the program will use. The constants shown in Listing 2-1 are correct for the Heath/Zenith terminals.
Some terminals do not use control sequences to control reverse video. Instead, they do it with the most significant bit of the output character. If it is 1, the character appears in reverse video. In that case, Output needs no memory of the prior character and the program becomes simpler. For such a terminal we could delete the two sequences, the Switch, and the subroutines GoToNormal and GoToReverse. In Output, we would simply OR the output character with the desired switch value, UsedNormal or UsedRevers.
CDUMP is useful for examining ASCII files. With it, you can quickly verify that the output of a new program contains exactly the letters and lines that it should. The program is not, however, of much use for looking at files of binary data, such as .COM files and direct-access files built by Microsoft BASIC. When looking at such files we need to see the exact hexadecimal value of each byte. That is what VDUMP will give us.
VDUMP will display each physical record in two ASCII lines, using the same display format as CDUMP. In addition, it will show the hexadecimal value of each byte directly below that byte's display. In order to display two hex digits below a single character, VDUMP will use a vertical hex format. It will show the two hex digits of a byte directly below the character display, like this:
ABCDEF0123456789... 4444443333333333... 1234560123456789...
This command will contain one nicety lacking in FDUMP and CDUMP. It is possible to stop the output of those commands with control-s, as is usual in CP/M. Once they are stopped, they can be aborted with control-c. The display produced by VDUMP is much bulkier, however. There must be a way of killing it with a single key-stroke.
It's a pleasure to find that VDUMP can be produced with only a small modification of CDUMP. The characters are displayed first, through Output. Before displaying each one, its hex digits must be saved. Note that the byte must be saved before Output turns off its high bit, or the dump will not display the file correctly. When the display of a line of 64 characters is complete, the two lines of 64 hex digits can be printed below it.
The pseudo-code of VDUMP is shown in Figure 2-2. Two lines "test console: should we quit?" have been added to the main loop. If any key has been pressed while a three-line group was being typed, VDUMP will terminate.
The Output routine has been augmented with a call to StowHex, a subroutine with the job of putting characters representing the current byte's hex digits into the two new arrays HiHex and LoHex.
It occurred to me that the hexadecimal displays in HiHex and LoHex are typed just at the point where CDUMP and FDUMP typed CR, LF to end the line. Therefore, I specified a subroutine EndLine to perform the whole job of ending the line of characters and displaying the saved hexadecimal values.
Figure 2-2. Complete pseudo-code of program VDUMP.
program VDUMP (fileref) C is a byte RecNum is a word EOF is a flag Index is a count 0..128 Buffer is an array[0..127] of bytes—a record. ReversSeq, NormalSeq are constant strings, Switch is a switch that says which we used last, ..it has settings of UsedNormal and UsedRevers HexHi, HexLo are arrays[0..63] of bytes, HexIndex is an index over them. main: open file "fileref" if it doesn't exist, abort with "No file." initialize reading mechanism: make Buffer the file buffer, Index := 128 { no data } RecNum := 1 Switch := UsedNormal HexIndex := 0 C := Getchar while( not EOF ): TypeCRLF do 64 times: Output(A) C := Getchar end do. ShowRecord EndLine test console: should we quit? do 64 more times: Output(A) C := Getchar { EOF on 64th (128th) } end do. EndLine RecNum := RecNum+1 test console: should we quit? end while. GoToNormal {don't leave console video reversed} end main. Getchar : returns a byte and a flag ... just like FDUMP end Getchar. Output( X: a byte ) StoreHex(X) turn off high bit of X if (X is printable) then GoToNormal else translate X for display GoToRevers endif. TypeChar(X) end Output. GoToNormal: if we aren't in normal video, get there. GoToReverse: if we aren't in reverse video, get there. StoreHex(X) HexHi[HexIndex] := HexDisplayL(X) HexLo[HexIndex] := HexDisplayR(X) HexIndex := HexIndex + 1 end StoreHex. ShowRecord: GoToNormal {don't put record number in reverse video} TypeCRLF; TypeCRLF TypeXXXX(Record) end ShowRecord. EndLine: GoToNormal {don't display the hex part reversed} TypeCRLF type out HexHi; TypeCRLF type out HexLo; TypeCRLF TypeCRLF HexIndex := 0 end EndLine. end VDUMP.
The code of VDUMP is shown in Listing 2-2. The test of the console is implemented with a call to the TESTCON macro. This macro (from PROG.LIB) uses a BDOS service request to test the status of the console. If the BDOS indicates that a key has been pressed, the macro transfers control to the label given as its operand. If a key has not been pressed, execution continues exactly as if the macro had not been present. This last requirement makes its code somewhat involved; it has to restore all registers, and the flags, before branching to one of two destinations. The XTHL instruction makes it possible to store the branch decision on the stack before the flags on which the decision is based are lost.
The StowHex subroutine uses included subroutines to produce the characters representing hex digits. It indexes the two arrays HexLo and HexHi in a proper manner, one that does not make any assumptions about page boundaries. A beginner might note the different uses of XCHG in this subroutine and in EndLine.
The EndLine subroutine ends, not with the usual RET, but with a JMP to another subroutine. This is a common technique in assembly language programming, and a perfectly safe one. It is the exact equivalent of a CALL followed by a RET. However, it is one of the assembly techniques that has no counterpart in a high-level language, and it cannot be represented in a structured pseudo-code. Once one is well-trained in a structured style, it is impossible to write such code without a mental apology to some unnamed god.
Just for fun, I wrote three versions of VDUMP. One version was in BASIC and ran under an interpreter. It was pitifully slow, completely unsatisfactory for taking a quick look at a file. In addition, the program couldn't use the command operands. After the BASIC interpreter had loaded, and after it had loaded the program, the program had to ask for the name of the file to be displayed.
The second version was in Pascal, and was compiled to machine language. This program was an improvement in several ways. The finished program was a .COM file, and that particular compiler allowed the command operands to be read by the program. Except that it couldn't be stopped by hitting any key, it was operationally the same as the VDUMP command shown here. The source program was just half the length of VDUMP, and somewhat easier to write.
However, it could not keep up with the terminal. That is, it could not supply output at a steady rate of 9600 bits per second (about 1000 characters per second). The first line of each triplet was written perceptibly more slowly than the two lines of hex digits. Now, this is not a terribly significant flaw. Under other circumstances, I might have swallowed my impatience and kept the Pascal version of VDUMP. The extra time invested in assembly language bought a more flexible way of killing the display and a marginal increase in speed (which produces a subjective feeling of smooth, fast output). VDUMP can only barely be justified as an appropriate subject for assembly language.