; Sample program for Chapter Eight
; Demonstrates the use of many MASM features discussed in Chapter Six
; including label types, constants, segment ordering, procedures, equates,
; address expressions, coercion and type operators, segment prefixes,
; the assume directive, conditional assembly, macros, listing directives,
; separate assembly, and using the UCR Standard Library.
;
; Include the header files for the UCR Standard Library.  Note that the
; "stdlib.a" file defines two segments; MASM will load these segments into
; memory before "dseg" in this program.
;
; The ".nolist" directive tells MASM not to list out all the macros for
; the standard library when producing an assembly listing.  Doing so would
; increase the size of the listing by many tens of pages and would tend to
; obscure the real code in this program.
;
; The ".list" directive turns the listing back on after MASM gets past the
; standard library files.  Note that these two directives (".nolist" and
; ".list") are only active if you produce an assembly listing using MASM's
; "/Fl" command line parameter.


		.nolist
		include 	stdlib.a
		includelib	stdlib.lib
		.list



; The following statement includes the special header file for this
; particular program.  The header file contains external definitions
; and various data type definitions.

		include		matrix.a


; The following two statements allow us to use 80386 instructions
; in the program.  The ".386" directive turns on the 80386 instruction
; set, the "option" directive tells MASM to use 16-bit segments by
; default (when using 80386 instructions, 32-bit segments are the default).
; DOS real mode programs must be written using 16-bit segments.

		.386
		option	segment:use16



dseg		segment	para public 'data'

Rows		integer	?		;Number of rows in matrices
Columns		integer	?		;Number of columns in matrices


; Input line is an input buffer this code uses to read a string of text
; from the user.  In particular, the GetWholeNumber procedure passes the
; address of InputLine to the GETS routine that reads a line of text
; from the user and places each character into this array.  GETS reads
; a maximum of 127 characters plus the enter key from the user.  It zero
; terminates that string (replacing the ASCII code for the ENTER key with
; a zero).  Therefore, this array needs to be at least 128 bytes long to
; prevent the possibility of buffer overflow.
;
; Note that the GetArray module also uses this array.

InputLine	char	128 dup (0)


; The following two pointers point at arrays of integers.
; This program dynamically allocates storage for the actual array data
; once the user tells the program how big the arrays should be.  The
; Rows and Columns variables above determine the respective sizes of
; these arrays.  After allocating the storage with a call to MALLOC,
; this program stores the pointers to these arrays into the following
; two pointer variables.

RowArray	dword	?		;Pointer to Row values
ColArray	dword	?		;Pointer to column values.



; ResultArrays is an array of dope vectors(*) to hold the results
; from the matrix operations:
;
; [0]- addition table
; [1]- subtraction table
; [2]- multiplication table
; [3]- division table
;
; [4]- modulo (remainder) table -- if the symbol "DoMOD" is defined.
;
; The equate that follows the ResultArrays declaration computes the number
; of elements in the array.  "$" is the offset into dseg immediately after
; the last byte of ResultArrays.  Subtracting this value from ResultArrays
; computes the number of bytes in ResultArrays.  Dividing this by the size
; of a single dope vector produces the number of elements in the array.
; This is an excellent example of how you can use address expressions in
; an assembly language program.
;
; The IFDEF DoMOD code demonstrates how easy it is to extend this matrix.
; Defining the symbol "DoMOD" adds another entry to this array.  The
; rest of the program adjusts for this new entry automatically.
;
; You can easily add new items to this array of dope vectors.  You will
; need to supply a title and a function to compute the matrice's entries.
; Other than that, however, this program automatically adjusts to any new
; entries you add to the dope vector array.
;
; (*) A "Dope Vector" is a data structure that describes a dynamically
; allocated array.  A typical dope vector contains the maximum value for
; each dimension, a pointer to the array data in memory, and some other
; possible information.  This program also stores a pointer to an array
; title and a pointer to an arithmetic function in the dope vector.


ResultArrays	DopeVec	{AddTbl,Addition}, {SubTbl,Subtraction}
		DopeVec	{MulTbl,Multiplication}, {DivTbl,Division}

		ifdef	DoMOD
		DopeVec	{ModTbl,Modulo}
		endif

; Add any new functions of your own at this point, before the following equate:


RASize		=	($-ResultArrays) / (sizeof DopeVec)


; Titles for each of the four (five) matrices.

AddTbl		char	"Addition Table",0
SubTbl  	char	"Subtraction Table",0
MulTbl		char	"Multiplication Table",0
DivTbl		char	"Division Table",0

		ifdef	DoMOD
ModTbl		char	"Modulo (Remainder) Table",0
		endif

; This would be a good place to put a title for any new array you create.

dseg		ends




; Putting PrintMat inside its own segment demonstrates that you can have
; multiple code segments within a program.  There is no reason we couldn't
; have put "PrintMat" in CSEG other than to demonstrate a far call to a
; different segment.

PrintSeg	segment	para public 'PrintSeg'

; PrintMat-	Prints a matrix for the cross product operation.
;
;		On Entry:
;
;			DS must point at DSEG.
;			DS:SI points at the entry in ResultArrays for the
;			array to print.
;
; The output takes the following form:
;
;	Matrix Title
;
;	       <- column matrix values ->
;
;	^      *------------------------*
;	|      |                        |
;	R      |                        |
;	o      | Cross Product Matrix   |
;       w      |       Values           |
;	       |                        |
;       V      |                        |
;	a      |                        |
;	l      |                        |
;	u      |                        |
;	e      |                        |
;	s      |                        |
;	|      |                        |
;	v      *------------------------*


PrintMat	proc	far
		assume	ds:dseg


; Note the use of conditional assembly to insert extra debugging statements
; if a special symbol "debug" is defined during assembly.  If such a symbol
; is not defined during assembly, the assembler ignores the following
; statements:

		ifdef	debug
		print
		char	"In PrintMat",cr,lf,0
		endif


; First, print the title of this table.  The TTL field in the dope vector
; contains a pointer to a zero terminated title string.  Load this pointer
; into es:di and call PUTS to print that string.

		putcr
		les	di, [si].DopeVec.TTL
		puts

; Now print the column values.  Note the use of PUTISIZE so that each
; value takes exactly six print positions. The following loop repeats
; once for each element in the Column array (the number of elements in
; the column array is given by the Dim2 field in the dope vector).

		print				;Skip spaces to move past the
		char	cr,lf,lf,"       ",0	; row values.

		mov	dx, [si].DopeVec.Dim2	;# of times to repeat the loop.
		les	di, ColArray		;Base address of array.
ColValLp:	mov	ax, es:[di]		;Fetch current array element.
		mov	cx, 6			;Print the value using a
		putisize			; minimum of six positions.
		add	di, 2			;Move on to next array element.
		dec	dx			;Repeat this loop DIM2 times.
		jne	ColValLp
		putcr				;End of column array output
		putcr				;Insert a blank line.

; Now output each row of the matrix.  Note that we need to output the
; RowArray value before each row of the matrix.
;
; RowLp is the outer loop that repeats for each row.

		mov	Rows, 0			;Repeat for 0..Dim1-1 rows.
RowLp:		les	di, RowArray		;Output the current RowArray
		mov	bx, Rows		; value on the left hand side
		add	bx, bx			; of the matrix.
		mov	ax, es:[di][bx]		;ES:DI is base, BX is index.
		mov	cx, 5			;Output using five positions.
		putisize
		print
		char	": ",0

; ColLp is the inner loop that repeats for each item on each row.

		mov	Columns, 0		;Repeat for 0..Dim2-1 columns.
ColLp:		mov	bx, Rows		;Compute index into the array
		imul	bx, [si].DopeVec.Dim2	; index := (Rows*Dim2 +
		add	bx, Columns		;		columns) * 2
		add	bx, bx

; Note that we only have a pointer to the base address of the array, so we
; have to fetch that pointer and index off it to access the desired array
; element.  This code loads the pointer to the base address of the array into
; the es:di register pair.

		les	di, [si].DopeVec.Data	;Base address of array.
		mov	ax, es:[di][bx]		;Get array element

; The functions that compute the values for the array store an 8000h into
; the array element if some sort of error occurs.  Of course, it is possible
; to produce 8000h as an actual result, but giving up a single value to
; trap errors is worthwhile.  The following code checks to see if an error
; occurred during the cross product.  If so, this code prints "  ****",
; otherwise, it prints the actual value.

		cmp	ax, 8000h		;Check for error value
		jne	GoodOutput
		print
		char	"  ****",0		;Print this for errors.
		jmp	DoNext

GoodOutput:	mov	cx, 6			;Use six print positions.
		putisize			;Print a good value.

DoNext:		mov	ax, Columns		;Move on to next array
		inc	ax			; element.
		mov	Columns, ax
		cmp	ax, [si].DopeVec.Dim2	;See if we're done with
		jb	ColLp			; this column.

		putcr				;End each column with CR/LF

		mov	ax, Rows		;Move on to the next row.
		inc	ax
		mov	Rows, ax
		cmp	ax, [si].DopeVec.Dim1	;Have we finished all the
		jb	RowLp			; rows?  Repeat if not done.
		ret
PrintMat	endp
PrintSeg	ends





cseg		segment	para public 'code'
		assume	cs:cseg, ds:dseg


;GetWholeNum-	This routine reads a whole number (an integer greater than
;		zero) from the user.  If the user enters an illegal whole
;		number, this procedure makes the user re-enter the data.

GetWholeNum	proc	near
		lesi	InputLine	;Point es:di at InputLine array.
		gets

		call	Geti		;Get an integer from the line.
		jc	BadInt		;Carry set if error reading integer.
		cmp	ax, 0		;Must have at least one row or column!
		jle	BadInt
		ret

BadInt:		print
		char	Bell
		char	"Illegal integer value, please re-enter",cr,lf,0
		jmp	GetWholeNum
GetWholeNum	endp




; Various routines to call for the cross products we compute.
; On entry, AX contains the first operand, dx contains the second.
; These routines return their result in AX.
; They return AX=8000h if an error occurs.
;
; Note that the CrossProduct function calls these routines indirectly.

addition	proc	far
		add	ax, dx
		jno	AddDone		;Check for signed arithmetic overflow.
		mov	ax, 8000h	;Return 8000h if overflow occurs.
AddDone:	ret
addition	endp


subtraction	proc	far
		sub	ax, dx
		jno	SubDone
		mov	ax, 8000h	;Return 8000h if overflow occurs.
SubDone:	ret
subtraction	endp

multiplication	proc	far
		imul	ax, dx
		jno	MulDone
		mov	ax, 8000h	;Error if overflow occurs.
MulDone:	ret
multiplication	endp

division	proc	far
		push	cx		;Preserve registers we destory.

		mov	cx, dx
		cwd
		test	cx, cx		;See if attempting division by zero.
		je	BadDivide
		idiv	cx

		mov	dx, cx		;Restore the munged register.
		pop	cx
		ret

BadDivide:	mov	ax, 8000h
		mov	dx, cx
		pop	cx
		ret
division	endp


; The following function computes the remainder if the symbol "DoMOD"
; is defined somewhere prior to this point.

		ifdef	DoMOD
modulo		proc	far
		push	cx

		mov	cx, dx
		cwd
		test	cx, cx		;See if attempting division by zero.
		je	BadDivide
		idiv	cx
		mov	ax, dx		;Need to put remainder in AX.
		mov	dx, cx		;Restore the munged registers.
		pop	cx
		ret

BadMod:		mov	ax, 8000h
		mov	dx, cx
		pop	cx
		ret
modulo		endp
		endif



; If you decide to extend the ResultArrays dope vector array, this is a good
; place to define the function for those new arrays.





; The main program that reads the data from the user, calls the appropriate
; routines, and then prints the results.

Main		proc
		mov	ax, dseg
		mov	ds, ax
		mov	es, ax
		meminit


; Prompt the user to enter the number of rows and columns:

GetRows:	print
		byte	"Enter the number of rows for the matrix:",0

		call	GetWholeNum
		mov	Rows, ax

; Okay, read each of the row values from the user:

		print
		char	"Enter values for the row (vertical) array",cr,lf,0

; Malloc allocates the number of bytes specified in the CX register.
; AX contains the number of array elements we want;  multiply this value
; by two since we want an array of words.  On return from malloc, es:di
; points at the array allocated on the "heap".  Save away this pointer in
; the "RowArray" variable.
;
; Note the use of the "wp" symbol. This is an equate to "word ptr" appearing
; in the "matrix.a" include file.  Also note the use of the address expression
; "RowArray+2" to access the segment portion of the double word pointer.

		mov	cx, ax
		shl	cx, 1
		malloc
		mov	wp RowArray, di
		mov	wp RowArray+2, es

; Okay, call "GetArray" to read "ax" input values from the user.
; GetArray expects the number of values to read in AX and a pointer
; to the base address of the array in es:di.

		print
		char	"Enter row data:",0

		mov	ax, Rows	;# of values to read.
		call	GetArray	;ES:DI still points at array.



; Okay, time to repeat this for the column (horizontal) array.

GetCols:	print
		byte	"Enter the number of columns for the matrix:",0

		call	GetWholeNum	;Get # of columns from the user.
		mov	Columns, ax	;Save away number of columns.


; Okay, read each of the column values from the user:

		print
		char	"Enter values for the column (horz.) array",cr,lf,0

; Malloc allocates the number of bytes specified in the CX register.
; AX contains the number of array elements we want;  multiply this value
; by two since we want an array of words.  On return from malloc, es:di
; points at the array allocated on the "heap".  Save away this pointer in
; the "RowArray" variable.

		mov	cx, ax			;Convert # Columns to # bytes
		shl	cx, 1			; by multiply by two.
		malloc				;Get the memory.
		mov	wp ColArray, di		;Save pointer to the
		mov	wp ColArray+2, es	; columns vector (array).

; Okay, call "GetArray" to read "ax" input values from the user.
; GetArray expects the number of values to read in AX and a pointer
; to the base address of the array in es:di.

		print
		char	"Enter Column data:",0

		mov	ax, Columns		;# of values to read.
		call	GetArray		;ES:DI points at column array.


; Okay, initialize the matrices that will hold the cross products.
; Generate RASize copies of the following code.
; The "repeat" macro repeats the statements between the "repeat" and the "endm"
; directives RASize times.  Note the use of the Item symbol to automatically
; generate different indexes for each repetition of the following code.
; The "Item = Item+1" statement ensures that Item will take on the values
; 0, 1, 2, ..., RASize on each repetition of this loop.
;
; Remember, the "repeat..endm" macro copies the statements multiple times
; within the source file, it does not execute a "repeat..until" loop at
; run time.  That is, the following macro is equivalent to making "RASize"
; copies of the code, substituting different values for Item for each
; copy.
;
; The nice thing about this code is that it automatically generates the
; proper amount of initialization code, regardless of the number of items
; placed in the ResultArrays array.


Item		=	0

		repeat	RASize

		mov	cx, Columns		;Compute the size, in bytes,
		imul	cx, Rows		; of the matrix and allocate
		add	cx, cx			; sufficient storage for the
		malloc				; array.

		mov	wp ResultArrays[Item * (sizeof DopeVec)].Data, di
		mov	wp ResultArrays[Item * (sizeof DopeVec)].Data+2, es

		mov	ax, Rows
		mov	ResultArrays[Item * (sizeof DopeVec)].Dim1, ax

		mov	ax, Columns
		mov	ResultArrays[Item * (sizeof DopeVec)].Dim2, ax

		mov	ResultArrays[Item * (sizeof DopeVec)].ESize, 2

Item		=	Item+1
		endm



; Okay, we've got the input values from the user,
; now let's compute the addition, subtraction, multiplication,
; and division tables.  Once again, a macro reduces the amount of
; typing we need to do at this point as well as automatically handling
; however many items are present in the ResultArrays array.

element		=	0

		repeat	RASize
		lfs	bp, RowArray		;Pointer to row data.
		lgs	bx, ColArray		;Pointer to column data.

		lea	cx, ResultArrays[element * (sizeof DopeVec)]
		call	CrossProduct

element		=	element+1
		endm


; Okay, print the arrays down here.  Once again, note the use of the
; repeat..endm macro to save typing and automatically handle additions
; to the ResultArrays array.


Item		=	0

		repeat	RASize
		mov	si, offset ResultArrays[item * (sizeof DopeVec)]
		call	PrintMat
Item		=	Item+1
		endm


; Technically, we don't have to free up the storage malloc'd for each
; of the arrays since the program is about to quit.  However, it's a
; good idea to get used to freeing up all your storage when you're done
; with it.  For example, were you to add code later at the end of this
; program, you would have that extra memory available to that new code.

		les	di, ColArray
		free
		les	di, RowArray
		free

Item		=	0
		repeat	RASize
		les	di, ResultArrays[Item * (sizeof DopeVec)].Data
		free
Item		=	Item+1
		endm


Quit:		ExitPgm			;DOS macro to quit program.
Main		endp

cseg		ends

sseg		segment	para stack 'stack'
stk		byte	1024 dup ("stack   ")
sseg		ends

zzzzzzseg	segment	para public 'zzzzzz'
LastBytes	byte	16 dup (?)
zzzzzzseg	ends
		end	Main
