ex8.exe:ex8.obj geti.obj getarray.obj xproduct.obj matrix.a ml ex8.obj geti.obj getarray.obj xproduct.obj ex8.obj: ex8.asm matrix.a ml /c ex8.asm geti.obj: geti.asm matrix.a ml /c geti.asm getarray.obj: getarray.asm matrix.a ml /c getarray.asm xproduct.obj: xproduct.asm matrix.a ml /c xproduct.asm
externdef
statements for all externally defined routines.
; MATRIX.A ; ; This include file provides the external definitions ; and data type definitions for the matrix sample program ; in Chapter Eight. ; ; Some useful type definitions: Integer typedef word Char typedef byte ; Some common constants: Bell equ 07 ;ASCII code for the bell character. ; A "Dope Vector" is a structure containing information about arrays that ; a program allocates dynamically during program execution. This particular ; dope vector handles two dimensional arrays. It uses the following fields: ; ; TTL- Points at a zero terminated string containing a description ; of the data in the array. ; ; Func- Pointer to function to compute for this matrix. ; ; Data- Pointer to the base address of the array. ; ; Dim1- This is a word containing the number of rows in the array. ; ; Dim2- This is a word containing the number of elements per row ; in the array. ; ; ESize- Contains the number of bytes per element in the array. DopeVec struct TTL dword ? Func dword ? Data dword ? Dim1 word ? Dim2 word ? ESize word ? DopeVec ends ; Some text equates the matrix code commonly uses: Base textequ <es:[di]> byp textequ <byte ptr> wp textequ <word ptr> dp textequ <dword ptr> ; Procedure declarations. InpSeg segment para public 'input' externdef geti:far externdef getarray:far InpSeg ends cseg segment para public 'code' externdef CrossProduct:near cseg ends ; Variable declarations dseg segment para public 'data' externdef InputLine:byte dseg ends ; Uncomment the following equates if you want to turn on the ; debugging statements or if you want to include the MODULO function. ;debug equ 0 ;DoMOD equ 0
; 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 ;# 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 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 cols. 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
; GETI.ASM ; ; This module contains the integer input routine for the matrix ; example in Chapter Eight. .nolist include stdlib.a .list include matrix.a InpSeg segment para public 'input' ; Geti- On entry, es:di points at a string of characters. ; This routine skips any leading spaces and comma characters and then ; tests the first (non-space/comma) character to see if it is a digit. ; If not, this routine returns the carry flag set denoting an error. ; If the first character is a digit, then this routine calls the ; standard library routine "atoi2" to convert the value to an integer. ; It then ensures that the number ends with a space, comma, or zero ; byte. ; ; Returns carry clear and value in AX if no error. ; Returns carry set if an error occurs. ; ; This routine leaves ES:DI pointing at the character it fails on when ; converting the string to an integer. If the conversion occurs without ; an error, the ES:DI points at a space, comma, or zero terminating byte. geti proc far ifdef debug print char "Inside GETI",cr,lf,0 endif ; First, skip over any leading spaces or commas. ; Note the use of the "byp" symbol to save having to type "byte ptr". ; BYP is a text equate appearing in the macros.a file. ; A "byte ptr" coercion operator is required here because MASM cannot ; determine the size of the memory operand (byte, word, dword, etc) ; from the operands. I.e., "es:[di]" and ' ' could be any of these ; three sizes. ; ; Also note a cute little trick here; by decrementing di before entering ; the loop and then immediately incrementing di, we can increment di before ; testing the character in the body of the loop. This makes the loop ; slightly more efficient and a lot more elegant. dec di SkipSpcs: inc di cmp byp es:[di], ' ' je SkipSpcs cmp byp es:[di], ',' je SkipSpcs ; See if the first non-space/comma character is a decimal digit: mov al, es:[di] cmp al, '-' ;Minus sign is also legal in integers. jne TryDigit mov al, es:[di+1] ;Get next char, if "-" TryDigit: isdigit jne BadGeti ;Jump if not a digit. ; Okay, convert the characters that follow to an integer: ConvertNum: atoi2 ;Leaves integer in AX jc BadGeti ;Bomb if illegal conversion. ; Make sure this number ends with a reasonable character (space, comma, ; or a zero byte): cmp byp es:[di], ' ' je GoodGeti cmp byp es:[di], ',' je GoodGeti cmp byp es:[di], 0 je GoodGeti ifdef debug print char "GETI: Failed because number did not end with " char "a space, comma, or zero byte",cr,lf,0 endif BadGeti: stc ;Return an error condition. ret GoodGeti: clc ;Return no error and an integer in AX ret geti endp InpSeg ends end
; GETARRAY.ASM ; ; This module contains the GetArray input routine. This routine reads a ; set of values for a row of some array. .386 option segment:use16 .nolist include stdlib.a .list include matrix.a ; Some local variables for this module: localdseg segment para public 'LclData' NumElements word ? ArrayPtr dword ? Localdseg ends InpSeg segment para public 'input' assume ds:Localdseg ; GetArray- Read a set of numbers and store them into an array. ; ; On Entry: ; ; es:di points at the base address of the array. ; ax contains the number of elements in the array. ; ; This routine reads the specified number of array elements ; from the user and stores them into the array. If there ; is an input error of some sort, then this routine makes ; the user reenter the data. GetArray proc far pusha ;Preserve all the registers push ds ; that this code modifies push es push fs ifdef debug print char "Inside GetArray, # of input values =",0 puti putcr endif mov cx, Localdseg ;Point ds at our local mov ds, cx ; data segment. mov wp ArrayPtr, di ;Save in case we have an mov wp ArrayPtr+2, es ; error during input. mov NumElements, ax ; The following loop reads a line of text from the user containing some ; number of integer values. This loop repeats if the user enters an illegal ; value on the input line. ; ; Note: LESI is a macro from the stdlib.a include file. It loads ES:DI ; with the address of its operand (as opposed to les di, InputLine that would ; load ES:DI with the dword value at address InputLine). RetryLp: lesi InputLine ;Read input line from user. gets mov cx, NumElements ;# of values to read. lfs si, ArrayPtr ;Store input values here. ; This inner loop reads "ax" integers from the input line. If there is ; an error, it transfers control to RetryLp above. ReadEachItem: call geti ;Read next available value. jc BadGA mov fs:[si], ax ;Save away in array. add si, 2 ;Move on to next element. loop ReadEachItem ;Repeat for each element. pop fs ;Restore the saved registers pop es ; from the stack before pop ds ; returning. popa ret ; If an error occurs, make the user re-enter the data for the entire ; row: BadGA: print char "Illegal integer value(s).",cr,lf char "Re-enter data:",0 jmp RetryLp getArray endp InpSeg ends end
; XProduct.ASM- ; ; This file contains the cross-product module. .386 option segment:use16 .nolist include stdlib.a includelib stdlib.lib .list include matrix.a ; Local variables for this module. dseg segment para public 'data' DV dword ? RowNdx integer ? ColNdx integer ? RowCntr integer ? ColCntr integer ? dseg ends cseg segment para public 'code' assume ds:dseg ; CrossProduct- Computes the cartesian product of two vectors. ; ; On entry: ; ; FS:BP- Points at the row matrix. ; GS:BX- Points at the column matrix. ; DS:CX- Points at the dope vector for the destination. ; ; This code assume ds points at dseg. ; This routine only preserves the segment registers. RowMat textequ <fs:[bp]> ColMat textequ <gs:[bx]> DVP textequ <ds:[bx].DopeVec> CrossProduct proc near ifdef debug print char "Entering CrossProduct routine",cr,lf,0 endif xchg bx, cx ;Get dope vector pointer mov ax, DVP.Dim1 ;Put Dim1 and Dim2 values mov RowCntr, ax ; where they are easy to access. mov ax, DVP.Dim2 mov ColCntr, ax xchg bx, cx ; Okay, do the cross product operation. This is defined as follows: ; ; for RowNdx := 0 to NumRows-1 do ; for ColNdx := 0 to NumCols-1 do ; Result[RowNdx, ColNdx] = Row[RowNdx] op Col[ColNdx]; mov RowNdx, -1 ;Really starts at zero. OutsideLp: add RowNdx, 1 mov ax, RowNdx cmp ax, RowCntr jge Done mov ColNdx, -1 ;Really starts at zero. InsideLp: add ColNdx, 1 mov ax, ColNdx cmp ax, ColCntr jge OutSideLp mov di, RowNdx add di, di mov ax, RowMat[di] mov di, ColNdx add di, di mov dx, ColMat[di] push bx ;Save pointer to column matrix. mov bx, cx ;Put ptr to dope vector where we can ; use it. call DVP.Func ;Compute result for this guy. mov di, RowNdx ;Index into array is imul di, DVP.Dim2 ; (RowNdx*Dim2 + ColNdx) * ElementSize add di, ColNdx imul di, DVP.ESize les bx, DVP.Data ;Get base address of array. mov es:[bx][di], ax ;Save away result. pop bx ;Restore ptr to column array. jmp InsideLp Done: ret CrossProduct endp cseg ends end