Documentation for the Modula-2 PrintF/ScanF support Modules. Don Milne, 28th January 1988
The modules which comprise the Modula-2 PrintF support are as follows:-
PrintFBase - Base module used by all other PrintF modules. SPRINTF - PrintF with output directed to strings FPRINTF - " " " " " char files. PRINTF - " " " " " the display.
The ScanF modules are similarly laid out:-
ScanFBase - Base module used by all other ScanF modules SSCANF - Take input from a string FSCANF - Take input from a character file SCANF - Take input from the keyboard.
If you can I would recommend that you look in any good book on C for a definition of the C versions of PrintF and ScanF (eg "A C Reference Manual" by Harbison & Steele, or the original K&R definition).
-------- PrintF ----------
The PrintF modules provide a method of output which is totally unlike that found in the usual M2 I/O modules. In the latter you are expected to import a different output procedure for each type that you wish to display. This can be incredibly tedious to use, and can turn what would have been a simple output statement in Pascal or C into a screenful of I/O calls. Needless to say this has the effect of obscuring the rest of the program which is not concerned with I/O.
We have borrowed the PrintF idea from the C language, mainly because it is an available solution to the problem which almost anyone who has used C or has read a book on C can recognise. The idea is to replace all of Modula's various output statements with the single one "PrintF".
The basic parameter supplied to PrintF is called the "control string". In the simplest case this string is merely a sequence of characters to be output, eg:-
would write 'Hello, World' to the display. The control string is special however in that besides allowing you to specify characters for output, you may also embed commands in the string which specify how other objects should be output, and if you do so you must supply those objects as further parameters to the PrintF call. Take the following example:-
PrintF('I am %i today.', age);
This outputs the string 'I am today'. The is replaced in the output with whatever you supply as the second parameter, however the '%i' part of the control strings says that this parameter MUST be an integer variable (however it can be of any integer type, eg SHORTINT, INTEGER or LONGINT depending on what your compiler supports). In the example given, if the integer variable 'age' equaled 12 then the string 'I am 12 today.' would be output.
You may have several of these % directives in the control string, but each one must be matched by another parameter supplied to PrintF. Now it is unfortunate but true that Modula-2 does not allow procedures to take a variable numbers of parameters. To get around this several variants of PrintF are supplied (PrintF, PrintF1, PrintF2 etc) which differ only in the number of parameters that it takes. All PrintF calls take at least the control string, so the number of parameters referred to does not include the string (ie PrintF1 takes the control string plus one parameter).
Field specifiers ----------------
The format of all field specifiers is:-
'%' [optional formatting directive]
The type character can be any character from the set 'a'..'z', or 'A'..'Z'. PrintF is case sensitive so watch out for that, ie 'a' and 'A' are not treated as the same character. These are the type characters currently supported by PrintF:-
'i' - output a decimal INTEGER, SHORTINT or LONGINT 'u' - output a decimal CARDINAL, SHORTCARD or LONGCARD 'c' - output a CHAR 's' - output a string (ie an ARRAY OF CHAR) 'h' - output the parameter in lower case hex (eg 0fff). The parameter can be any of the types accepted by 'i','u' or 'c'. 'H' - as 'h', but the hex output is upper case (eg 0FFF). 'x' - as 'h' 'X' - as 'H'
A longer example:-
PrintF5('My name is %s, aged %u, born %u/%u/%u', name, age, day, month, year);
If name='Sam', age=20, day=10, month=1, year = 70 then the output of PrintF would be:-
'My name is Sam, aged 20, born 10/1/70'
All the examples shown so far do not specify any formatting of the the object to be output. If you want to do this you place the directive between the '%' and the type character. For example, if you wanted that date to be output dd/mm/yy then you would change that part of the control string to:-
These are the formatting directives at your disposal:-
- specifies the minimum width of the field, eg %2u specifies a minimum width of 2 characters. The field will be padded out with spaces unless you put a leading zero in front of the width, in which case the field would be padded out with zeroes, eg %3u would produce ' 1' and %03u would produce '001'.
The field width can be followed by '.' and another decimal number, and this specifies a number of decimal places for a floating point parameter, eg '%5.2r' would produce a real number formatted with a total minimum field width of 5, and 2 decimal places. Note that floating point output is not built into the standard PrintF, if you need it you have to import an extension module which installs itself into the PrintF library.
Please remember that the field width specifies a *minumum* field width. This means that if the conversion produces a string whose length is less than the width then it will be padded. If the length is greater then it will be output as is - it will NOT be truncated to the field width!.
'*' This directive tells PrintF that the fieldwidth should be taken from a supplied parameter instead of from the control string. Examples of this are '%*u' and '%*.*r'.
'-' This directive tells PrintF that the field should be left justified (the default is right justification). This directive has no effect if no field width is given.
'+' This directive means that all decimal output should be signed, so positive numbers would have a leading '+'.
' ' A space character means that all decimal output should be signed if negative, or a space should be output if not.
Some examples of the above:-
'%-30s' A string parameter is supplied, and should be padded to a width of 30, left justified.
'%+i' An integer parameter is supplied and should be signed when output, either with '-' or '+'.
'%05i' An integer is expected, and should output with a field width of 5, padded with zeroes.
'%+05i' As above, but always sign the number.
'%04H' An integer, cardinal or char is expected, and is output in upper case hex padded with '0's to a width of 4 digits.
'%%' Produce the character '%'.
Switch Characters ----------------- Switch characters provide a way of embedding non-printable characters in the control string so that they can be output by PrintF. The format of all switch characters is:-
"Character Def" will be one of the following:-
nnn - A three digit *decimal* number specifies the ascii code of the character to be output, eg '\001' means CTRL-A.
'n' - A newline character sequence is output. What characters these actually consist of is implementation dependent. On DOS systems it will be CR-LF. Example- '\n'
'r' - A single carriage return is output. This is not the same as newline, instead it has the effect of moving the cursor to the first position on the current line.
't' - Output a tab character (ascii 9)
'f' - Output a formfeed character (ascii 12)
'b' - Output a backspace character (ascii 8)
------------- ScanF -----------------
ScanF is similar to PrintF, but deals with input rather than output. ScanF always has the form:-
ScanF( , , )
In SSCANF the input source is a string. In FSCANF the input source is a file variable, and in SCANF the input source is the keyboard.
ScanF control strings, like PrintF's, consist of a mixture of ascii characters, field specifiers and switch characters. The interpretation is slightly different however. In the ScanF the control string is determining what input is converted to one of the vars, and also what characters are skipped in the input.
This is a simple ScanF call:-
ScanF('%u %u %u', num1, num2, num3);
This call tells ScanF to read three decimal numbers from the keyboard and return them in the variables num1, num2 and num3. The spaces in the control string are included for clarity but are not really required.
Field widths may also be specified in ScanF control strings, but unlike PrintF these are treated as MAXIMUM field widths, for example if the input to a ScanF call was '123456789012', and the ScanF call itself was:-
ScanF3('%4u %4u %4u', num1, num2, num3);
Then the results returned would be num1=1234, num2=5678, num3=9012. Notice that ScanF stops building the number when it has read fieldwidth characters from the input.
We have included spaces in this example, but again it was not necessary. A space actually tells ScanF to skip any whitespace characters before starting the conversion, but that is done by default for numeric input in any case.
ScanF works as follows. If the control string contains an ascii character then input is skipped until that character is met (this differs from the C convention by the way, in that language the character is expected to be the next one in the input, but then you have to specify many more characters). The same applies to any switch characters given. If ScanF meets a field specifier then a convertion is performed. The range of possible specifiers is as defined by PrintF:-
'i' - read an INTEGER, SHORTINT or LONGINT. Whitespace is skipped before this conversion.
'u' - read CARDINAL, SHORTCARD or LONGCARD. Whitespace is skipped.
'c' - Read a CHAR. Whitespace is NOT skipped.
's' - Read a string. The treatment of whitespace depends on whether a field width has been specified. If no field width is given then white space is skipped, and characters are read and stored until a whitespace character is met (EOF or EOL are both treated as whitespace). If a field with IS specified then no initial whitespace is skipped and ScanF continues until fieldwidth characters are read or until EOF.
'h' - read a hex value. The dest variable can be any numeric type (except FP types), or it can be a CHAR.
'H','x','X' - same as 'h'
Field widths in ScanF ---------------------
As mentioned above the ScanF field width specifies a MAXIMUM width for the field. The only other thing to watch out for is the '*' directive. In ScanF this causes what in C literature is referred to as "conversion suppression". What this actually means is that the value is read from the input but is then thrown away - none of the parameters are used to retain the result. To return to our last example, if the input to ScanF is still '123456789012', and the ScanF call itself is:-
ScanF2('%4u %*4u %4u', num1, num2);
Then the results will be num1=1234, and num2=9012. This is because although the second 4 digits have been read they are simply discarded and do not use up one of the vars, hence only two are supplied. This is the only exception to the rule that for every field specifier there must be a matching variable supplied as a parameter.