Since it is impossible to predict appropriate interfaces for all the bizarre I/O devices Ada is likely to use, she was given language features that make it possible to design custom I/O services. Packages, tasking, and the low level interfaces described in MIL-STD-1815A chapter 13, can be used to interface Ada to anything you can imagine.
Embedded computer applications are so unique, it is unlikely I could write reusable embedded computer I/O routines with broad appeal. General purpose applications, on the other hand, often involve an ASCII interface to a CRT terminal. This section, therefore, contains some I/O routines I think you will find useful in general purpose applications. Although these packages don't often apply directly to embedded computer applications, the lessons learned from this section do.
I often find it annoying that the IMAGE attribute for integers adds a single blank space on the beginning of the string when the number is positive. This causes problems when I try to insert numbers in the middle of a line of text. If I leave a space before the number, then it appears that there were two spaces in front of positive numbers. If I don't leave a space, then negative numbers appear to be hyphenated to the word immediately before it.
The IMAGE attribute for integers also has the sometimes annoying characteristic of returning a string of unpredictable length. When it converts a number to a character string, it uses only as many characters as it needs. This is often desirable because it is frequently necessary to insert a number in the middle of a line of text, and it would look funny if there were lots of leading spaces. There are other times, though, when you want the number to be converted to a string with a certain number of characters regardless of the value of the number. For example, when converting the internal representation of 14 February, 1988, to the 8 character string "02/14/88" for output, you want 2 characters each for the month, day, and year. Or you might want to give the file containing the data from flight number 375 the name "FL00375.DAT". You will need a routine to convert flight numbers to a 5 character string with leading zeros.
Later in this section you will see a package called the VIRTUAL_TERMINAL. The first version of this package body depended on the IMAGE attribute. To more the cursor to line seven column fifteen it was necessary to generate the string ASCII.ESC & "[7;15H". Figure 17 shows all the trouble I had to go through to get rid of leading spaces and adapt to the variable length strings produced by the IMAGE attribute.
These little quirks in the IMAGE attribute make it awkward to use in many applications, so I wrote the Image function in ASCII_UTILITIES. It does the same thing the IMAGE attribute does for integers, but it gives me the control I need over the length of the string and leading characters. Figure 18 shows how much easier it is to use the Image function than the IMAGE attribute.
Many people are surprised to discover the IMAGE attribute is not defined for the type float. That's because the attribute is defined only for discrete types, and type float is not a discrete type. I can understand why the LRM didn't require an IMAGE attribute for all data types, because the image of a composite data type can be complicated, especially if it is a record that has components that are composite data types. That doesn't mean it is impossible to define an IMAGE attribute for composite data types. In fact, Ada debuggers have to be able to display the contents of composite data types, and I have seen a couple that use aggregate notation to do that.
The lack of an IMAGE attribute for real data types is a nuisance if you want to output real numbers. You have no choice but to instantiate FLOAT_IO. Since I often don't use TEXT_IO, I didn't like that choice. Compilers are starting to get to the point where they can eliminate dead code, so linking all of TEXT_IO just to get a couple of string conversions may not be as bad as it used to be, but I still don't like to do it. The Fixed_Image and Float_Image functions in ASCII_UTILITIES give you better alternatives than instantiating FLOAT_IO.
The Fixed_Image function will display a floating point number in fixed notation. You can select the number of characters before and after the decimal point. If you don't specify these values, the default is the minimum number of characters before the decimal point, and two characters after it.
The Float_Image function displays real numbers in exponential form. I thought about designing it so that it would allow you to specify the number of digits before the decimal point, but decided against it. When people output numbers in exponential form, they don't normally stick them in the middle of a line of text, and they don't normally want a variable number of digits before the decimal point. Numbers printed in exponential notation usually appear in columns on printouts, and I suspect people often just glance at the single digit in front of the decimal, and the exponent, to get an idea of the magnitude of the number. The numbers should line up neatly in the columns, so I precede the number with a leading space if it is positive. There isn't any way I can predict how many digits should be printed after the decimal point, so I made that a parameter. (I set the default to 5 digits to enforce my disposition toward 6 digit floating point numbers.)
I considered a single image function that would figure out if the user wanted fixed- or floating-point format, just as FLOAT_IO does. I decided not to for three reasons. First, combining the two routines results in a more complicated, and therefore less reliable, routine. Two simpler routines are less likely to contain an error, and are easier to test, than a single complicated one. Second, combining two similar but different routines sometimes forces some design trade- offs. For example, I wanted an AFT default of 2 for fixed format and 5 for exponential format. If I combined them I would have had to pick 2 or 5, or force the programmer to supply a value every time it is used. Third, the two names Fixed_Image and Float_Image make the program more readable. The reader doesn't have to know to look at the EXP field to see if it is zero or not, to tell him if the number will be printed in fixed- or floating-point format.
I considered making all the image functions in ASCII_UTILITIES generic, and decided not to. There isn't any need for generics. The Dimensionless functions can be used to convert dimensioned data types to Integer_32 or float and normal type conversions can be used to convert other special numeric data types to Integer_32 or float. Then the appropriate image function can be used on the equivalent Integer_32 or float value.
There isn't a predefined IMAGE attribute that will convert an integer from 0 to 9 (or 0 to F in hexadecimal) to the corresponding ASCII character. This would be a handy attribute to have if you needed to write a utility program that outputs files in octal or hexadecimal, or you wanted to print address locations in octal or hexadecimal. Several years ago I published a function in the Journal of Pascal, Ada & Modula-2 that fills this need. In those days I called it ASCII_Code_For, but to make it consistent with the other similar functions in ASCII_UTILITIES it is now called Image. This function doesn't let you specify the length of the return string because it doesn't return a string, it returns a single character. Instead it has a second parameter to let you specify the number base. This is necessary for it to check for digits out of range.
There are three simple solutions. The first is to change the name of the character Image function back to ASCII_Code_For. I don't like that solution. (If I did, I wouldn't have changed it in the first place.) The second solution is to break the statement into two statements, making it clear what you want to do. For example, if you want to print an eight character string image, you could do the following:
I prefer a third solution, which uses a qualified expression. The qualified expression is a rarely used Ada feature, but it comes in handy in situations like these. It is a way for you to tell Ada what you really mean. In this case it looks like this:
I have no complaint with Ada's standard VALUE attribute for discrete types, so I haven't written another version of it. The deficiency is that VALUE, like IMAGE is only defined for discrete types. Ada does not have a VALUE attribute for real numbers.
If she did, I probably wouldn't like it. Ada would probably be fussy about the format, just as she is for real literals in a program. That's fine for programmers. If a programmer wants to specify a real value he should be smart enough to know the correct syntax. You can't expect that kind of knowledge from a user. If you write software for military applications you have to remember that lack of a degree in computer science does not disqualify someone from joining the ranks of enlisted personnel. If you prompt a marine with "Enter the distance to the enemy position (in miles)", you better be prepared to accept "12" as an answer. If you insist on "1.20e01", the Marine is more likely to smash the weapon to bits than to figure out what you mean by "Data Entry Error!"
The Value function is complicated because it accepts any reasonable input and converts it to a floating point number. Real computer scientists will probably find this tolerance offensive, but that's the way it has to be in the battlefield. (If I were writing this function for use in a compiler, I would have required strict Ada syntax. You are permitted, even encouraged, to modify the Value function to make it less tolerant if that would make it more appropriate for your application.)
There is a Value function in ASCII_UTILITIES which converts a character in the range '0'..'9' or 'A'..'F' to a number from 0 to 15. It is the inverse of the single character Image function. It checks to make sure the input character is valid for the number base specified. The default is, of course, base 10.
You shouldn't need a qualified expression to distinguish the two value functions because both the input and output types are different. It is hard to imagine an ambiguous situation that might occur naturally, although I'm sure you could create one if you are devious enough.
Suppose the string is " +.". The pointer I has skipped over the leading blanks, and is pointing to the plus sign. It is true that S(I..I+1) = "+.". When the case structure tries to evaluate S(I+2), that is outside the range of S, a CONSTRAINT_ERROR will be raised. That's fine, because that's what I would do if S(I+2) existed and was not a digit. The CONSTRAINT_ERROR isn't a problem, it is a beneficial side effect that I have taken advantage of.
That isn't always the case. Consider this section of code just a little farther down the listing. I have replaced the short circuit control form (AND THEN) with the usual AND statement to illustrate a potential problem.
This part of the routine finds the first and last characters of the whole part of the number. That is, if the number is "-123.45", the routine will leave FIRST pointing to the number 1 and LAST pointing to the number 3, so S(FIRST..LAST) will be the string "123". This will work without error. Suppose, however, the string was "123". FIRST points to the 1, but CONSTRAINT_ERROR might be raised looking for the 3.
The problem is in the statement while I <= S_LAST and S(I) in '0'..'9' loop when I = S_LAST+1. If the program tries to evaluate S(S_LAST+1) to see if it is in '0'..'9', CONSTRAINT_ERROR will be raised. You can't be sure the program will check I <= S_LAST and realize the whole expression must be false before finding the character in S(S_LAST+1) because section 4.5 paragraph 5 of the LRM says that they are "evaluated in some order that is not defined by the language."
The solution is to use the short circuit control form. The statement while I <= S_LAST and then S(I) in '0'..'9' loop tells the computer to evaluate I <= S_LAST first, AND THEN evaluate S(I) only if the first part is true. You will find several examples of the short circuit control form AND THEN in the ASCII_UTILITIES.Value function.
These text processing functions can be used in combination to make an enumeration output look prettier. For example, a poker program may have an enumeration type with the value THREE_OF_A_KIND that must be displayed. The IMAGE attribute first converts it to the string "THREE_OF_A_KIND". The Lower_Case string function can then convert all the letters to lower case, and the Change string function can replace the underlines with blank spaces. This produces "three of a kind". If you like, you can use the Upper_Case character function to capitalize the first character to get "Three of a kind".
When using fixed strings, is is occasionally necessary to add spaces to the end of a short string to fill out a variable that was declared as a longer string. It might also be necessary to truncate a long string to make it fit in a shorter string. The String_Copy procedure was designed to do this.
String_Copy can be useful when dealing with enumeration images. For example, if type Colors is (RED, GREEN, BLUE); and you use Colors'IMAGE to convert a value to a character string, the result may be 3, 4 or 5 characters. If you want the result to always be five characters, you can do the following:
1. Dr. Sylvan Rubin, Dynamic String Functions in Ada, Journal of Pascal, Ada & Modula-2, Vol. 3, No. 6, Nov./Dec. 1984.
2. Do-While Jones, Ada Dynamic Strings Revisited Part 2, Journal of Pascal, Ada & Modula-2, Vol. 5, No. 3, May/June 1986.