IOStream Routines


UCR Standard Library: IOStream Routines
9.1 - Interface
9.2 - Overview of the Stream Output Facilities
9.3 - COUT
9.3.1 - Calling Conventions and Assertions
9.3.2 - Syntax & Examples
9.4 - CIN
9.4.1 - Calling Conventions and Assertions
9.4.2 - Syntax & Examples
9.5 - Extending COUT and CIN to Handle Other Data Types


UCR Standard Library: IOStream Routines

The IOStream package provides a simplified I/O system for assembly language programmers. It somewhat parallels the standard output iostream COUT and CIN facilities found in the C++ programming language.


9.1 Interface

To access the routines in the declarations package, your assembly language module must include the file "iostream.a" during assembly. You can accomplish this with either of the following include statements in your assembly code:




	include	iostream.a
or
	include	ucrlib.a

The iostream.a include file exports several symbols. The UCR Standard Library prefaces all "private" names with a dollar sign ("$"). You should not call any routine in this package that begins with this symbol unless otherwise advised. To avoid name conflicts, you should not define any symbols in your programs that begin with a dollar sign ("$"). Note that future versions of the stdlib (that remain compatible with this release) may change "private" names. To remain compatible with future releases, you must not refer to these "private" names within your programs.

Source code appearing in this chapter is current as of Version Two, Release 40. There may be minor changes between this source code and the current release.


9.2 Overview of the Stream Output Facilities

The IOStream package provides a macro that simulates the COUT stream output and CIN stream input operators in C++. These macro, in conjunction with the DCL.A package, automatically figure out the type of their operands and print or read them in an appropriate fashion. These macros greatly simplify reading and writing data through the standard input/output devices, especially for beginners and for "quick and dirty" programming.

For slightly better control of the output data, you should also take a look at printf and printff in the standard output package.


9.3 COUT

The COUT macro requires one or more operands. It attempts to decipher the type of the operands and print them using an appropriate format. A typical COUT invocation takes the form:




	cout	operand1, operand2, ...

If an operand is an identifier declared within the VAR..ENDVAR using a standard type definition macro (e.g., integer), then COUT will print the value of that variable using its native format. The following lists the possible output formats:

If the COUT operand is an 80x86 register, COUT can't really determine the data type of the corresponding value, so it prints the value of the register using hexadecimal notation (two digits for eight bit registers, four digits for 16-bit registers, and eight digits for 32-bit registers).

If the COUT operand is a constant, COUT will print that constant as an ASCII character. Similarly, if the COUT operand is a string, COUT will print that string literal to the standard output device.

If the COUT operand is a byte, sbyte, word, sword, dword, or sdword variable, COUT will print type(xxxx) where type is the type name (byte, sbyte, word, etc) and xxxx is the hexadecimal representation of that value. Since programmers can store many different types into these types of variables, COUT does not try to interpret the results.

If you supply any other operand type to COUT, MASM will generate an error.

If you create your own types using the DCLTYPE macro, you can provide a macro that will allow COUT to print your data type. For more details, see the DCLTYPE macro in the chapter on declarations (DCLS) and the section on extending CIN and COUT later in this chapter.


9.3.1 Calling Conventions and Assertions


9.3.2 Syntax & Examples




	cout "Hello world",13,10

The following is a more typical use of the COUT statement:




	cout "I=",i, " F=",F, " string='",string,"'",nl

	I=10 F= 1.500000e+00 'Hello There'


9.4 CIN

CIN provides an easy way to input values for variables you declare with TYPEDCL type macros. Unlike COUT, CIN only allows variable names as operands and you must declare these variables using type declaration macros in the VAR..ENDVAR section. CIN will not input values into registers, variables declared with MASM directives, or other objects.

CIN supports the input of all stdlib predefined variable types. It calls the following standard input routines for each of these types:

You can extend CIN so that it can read values of types that you define. See the appropriate section later in this chapter.


9.4.1 Calling Conventions and Assertions


9.4.2 Syntax & Examples




var
        integer intval
        float fpval
        string str[128]
endvar

                 .
                 .
                 .
                cout    "Enter an integer and a floating point value:"
                cin     intval, fpval
                cout    "Enter a line of text:"
                cin     str
                 .
                 .
                 .

9.5 Extending COUT and CIN to Handle Other Data Types

The DCLS.A and IOSTREAM.A packages work together to simplify I/O programming in assembly language. In particular, the types you create with the DCLTYPE macro (including the stdlib's predefined types) communicate type information to CIN and COUT so these routines can call the appropriate stdout or stdin routines to print or input the associated variable.

Consider the following type declaration:




	dcltype	LHex, dword			;A 32-bit hexadecimal value

	LHex	LHvar1
	LHex	LHVar2=0FFFF0000h
	LHex	LHvar3[20], LHVar4[10]=0FFFFh
	LHex	*LHvar5, *LHvar6=LHVar5, *LHVar7[4], *LHVar8[4]=LHVar5

Whenever you declare a variable using one of these type macros, the macro emits the appropriate MASM statements to allocate storage for the variable. For simple variables (like LHvar1 above), the type macro emits the directive specified by the second operand to the DCLTYPE macro (dword in this case):




	LHvar1	dword	?

When you specify an initial value (as for LHVar2, above), the LHex macro substitutes the initial value for the "?" in the operand field of the MASM directive, e.g.,




	LHVar2	dword	0FFFF0000h

When you declare an array by adding a subscript to the name, the LHex macro generates an appropriate "dup" operator in the operand field of the dword directive:




	LHVar3	dword	20 dup (?)
	LHVar4	dword	10 dup (0FFFFh)

If you place an asterisk in front of the variable name, the type macro will use the "dword" directive to reserve four bytes of storage for a far pointer (this isn't obvious in this case since the LHex macro emits a dword directive anyway). If you specify an array or an initial value, the type directive emits the appropriate dword operand:




	LHvar5	dword	?
	LHVar6	dword	LHVar5
	LHVar7	dword	4 dup (?)
LHVar8	dword	4 dup (LHVar5)

Consider one more example that clearly demonstrates what is happening when you declare a pointer variable:




	dcltype	SInt, sbyte			;Short integer: -128..+127
	SInt	si, *sip

	si	sbyte	?
	sip	dword	?

Note that from a C++ point of view the standard library probably should have used the "&" symbol rather then "*" to denote a reference (pointer) variable since the CIN and COUT routines automatically dereference pointer variables. However, the "&" symbol is an operator in MASM and it was easier to use the "*" symbol.

In addition to emiting the directives that allocate space for a variable you declare, the type declaration macros also emit a text equate for each symbol you define. The text equate always takes the following form:




	$?varname	textequ	<typename>

	LHex	H1, *H2=H1

	H1	dword	?
	$?H1	textequ	<LHex>
	H2	dword	H1
	$?H2	textequ	<*LHex>

If a variable appears in the operand field of the COUT statement, the COUT macro first checks to see if it is a type macro declared variable using the technique described above. If this is the case, then COUT will invoke one of two macros to print the variable's value:




	$$PV_typename  varname
or	$$PP_typename  varname

	cout	H1, H2

	$$PV_LHex H1
	$$PP_LHex H2

Although the type declaration macro can create the "$?varname" symbol for you, it is not smart enough to write the $$PP_typename and $$PV_typename macros for you. You must write these macros and they must take the following form:




$$PV_typename   macro   varname

        <code that prints varname's value>

                endm
                
$$PP_typename   macro   varnamePtr

        <code that fetches the data at
         address "varnamePtr" and prints it>

                endm

COUT automatically preserves the values in the EAX, ES, and DI registers. Therefore, you may use these registers within your macros without saving their contents. If you use any other registers, you must preserve their values within the macro if you expect COUT to preserve their values. Consider the LHex data type defined earlier:




$$PV_LHex       macro   Hex32
                mov     ax, word ptr Hex32+2
                putw
                mov     ax, word ptr Hex32
                putw
                endm
                
$$PP_LHex       macro   Hex32Ptr
                les     di, dword ptr Hex32Ptr
                mov     ax, es:[di+2]
                putw
                mov     ax, es:[di]
                putw
                endm


CIN interfaces with the DCLTYPE macro in a similar manner except you define $GP_typename and $GV_typename macros that read the data from the standard input. The following macros supply the input side of the LHex data type:




$$GV_LHex       macro   Hex32toRead
                getlh
                mov     Hex32ToRead, eax
                endm
                
$$GP_LHex       macro   Hex32Ptr2Rd
                getlh
                les     di, Hex32Ptr2Rd
                mov     es:[di], eax
                endm

Once you define the macros above, CIN and COUT will work properly with the new data type you've created. In addition to the above macros, you should also define a symbol $$typename whose value is the number of bytes required by the object, e.g.,




$$LHex	=	4

You can also use the stdlib ENUM statement (see the chapter on declarations) to declare new data types of which COUT and CIN are aware. Consider a statement like the following:




	enum	colors, <red, green, blue>
	typedcl	colors, byte




	enum colors, <red, green, blue$$PV_colors     macro   color
                mov     al, byte ptr color
                call    PrintColors
                endm
                
$$PP_colors     macro   color
                les     di, dword ptr color
                mov     al, es:[di]
                call    PrintColors
                endm
                
PrintColors     proc
                cmp     al, red
                jne     NotRed
                print   "red"
                ret
                
NotRed:         cmp     al, green
                jne     NotGreen
                print   "green"
                ret
                
NotGreen:       cmp     al, blue
                jne     NotBlue
                print   "blue"
                ret
                
NotBlue:        print   "Illegal Color!"
                ret
PrintColors     endp



$GV_Colors      macro   Color2Read
                call    GetColor
                mov     byte ptr Color2Read, al
                endm
                
$GP_Colors      macro   ColorPtr
                call    GetColor
                les     di, ColorPtr
                mov     es:[di], al
                endm
                


TstChar         macro   Chars
                forc    chr, <Chars>
                getc
                tolower
                cmp     al, '&chr&'
                jne     BadColor
                endm
                endm
                
                
GetColor        proc    near
                getc
                jc      BadColor        ;If GETC error.
                tolower                 ;Allow upper and lower case.
                cmp     al, "r"
                je      MustBeRed
                cmp     al, "g"
                je      MustBeGreen
                cmp     al, "b"
                je      MustBeBlue
                
; Illegal character.  Cannot be red, green, or blue at this
; point.  So if exceptions are enabled, raise an exception.

BadColor:       GetXEnabled
                cmp     ax, 0
                je      NoExceptions
                mov     ax, $Conversion
                Raise

; If exceptions are not enabled, just return with a bogus value.

                mov     ax, -1
                stc
                ret

MustBeRed:      tstchar ed
                mov     al, red
                ret
                
MustBeGreen:    tstchar reen
                mov     al, green
                ret
                
                
MustBeBlue:     tstchar lue
                mov     al, blue
                ret
GetColor        endp