# MASM Macros

* MASM is actually two languages rolled into a single program. The runtime language is the standard x86-64/MASM assembly language you’ve been reading about in all the previous chapters. This is called the runtime language because the programs you write execute when you run the executable file. MASM contains an interpreter for a second language, the MASM compile-time language (CTL). MASM executes the CTL program during assembly (compilation). Once MASM completes assembly, the CTL program terminates.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FnYmbQaIBoXz4axaj6TQc%2Fimage.png?alt=media&#x26;token=d0c8c38a-8bf3-492d-a459-221c278ce658" alt=""><figcaption></figcaption></figure>

* The CTL application is not a part of the runtime executable that MASM emits, although the CTL application can write part of the runtime program for you. By learning how to use the MASM CTL and applying it properly, you can develop assembly language applications as rapidly as high-level language applications.

## Compile-Time Expressions and Operators

#### Constants and variables

* You declare compile-time constants by using the textequ or equ directives. You declare compile-time variables by using the "=" directive.

```
inc_by equ 1
ctlVar = 0
ctlVar = ctlVar + inc_by
```

#### The MASM Escape (!) Operator

* The first operator is the ! operator. When placed in front of another symbol, this operator tells MASM to treat that character as text rather than as a special symbol.

#### The MASM Evaluation (%) Operator

* The second useful CTL operator is %. The percent operator causes MASM to evaluate the expression following it and replace that expression with its value.

```
num10 = 10
text10 textequ <10>
tn11 textequ %num10 + 1
```

#### The catstr Directive

* The catstr function has the following syntax:

```nasm
identifier catstr string1, string2, ...

; example

helloWorld catstr <Hello>, <, >, <World!!>
```

* The identifier is an (up to this point) undefined symbol. The string1 and string2 operands are textual data surrounded by < and > symbols. This statement stores the concatenation of the two strings into identifier.

#### The echo and .err Directives

```nasm
; CTL "Hello World" program

            echo    Listing 13-1: Hello, World!
            end
```

* The echo statement displays the textual representation of its argument list during the assembly of a MASM program. The above program will show -

```
Listing 13-1: Hello, world! 
```

* **%out** is a synonym for echo (just in case you see %out in any MASM source files)
* The .err directive, like echo, will display a string to the console during assembly, though this must be a text string (delimited by < and >). The .err statement displays the text as part of a MASM error diagnostic.
* Furthermore, the .err statement increments the error count, and this will cause MASM to stop the assembly (without assembling or linking) after processing the current source file.

```nasm
.err <Error message here>
```

#### The instr Directive

* The instr directive searches for the presence of one string within another.

```nasm
identifier instr start, source, search

; example

WorldPosn instr 1, <Hello World>, <World>
```

* where identifier is a symbol into which MASM will put the offset of the search string within the source string. The search begins at position start within source. Unconventionally, the first character in source has the position 1 (not 0).

#### The sizestr Directive

* The sizestr directive computes the length of a string

```
identifier sizestr string

; example

hwLen sizestr <Hello World>
```

* where identifier is the symbol into which MASM will store the string’s length, and string is the string literal whose length this directive computes.

#### The substr Directive

* The substr directive extracts a substring from a larger string

```nasm
identifier substr source, start, len

; example

hString substr <Hello World>, 1, 5
```

* where identifier is the symbol that MASM will create (type TEXT, initialized with the substring characters), source is the source string from which MASM will extract the substring, start is the starting position in the string to begin the extraction, and len is the length of the substring to extract.&#x20;
* The len operand is optional; if it is absent, MASM will assume you want to use the remainder of the string.

## Conditional And Repetitive Assembly

#### Conditional Assembly

* The simplest form of the MASM compile-time if statement uses the following syntax:

```nasm
if constant_boolean_expression
        Text
endif 
```

* The expression must be a constant expression that evaluates to an integer value.&#x20;
* The identifiers in a compile-time expression must all be constant identifiers or a MASM compile-time function call (with appropriate parameters). Because MASM evaluates these expressions at assembly time, they cannot contain runtime variables.
* The MASM if statement supports optional elseif and else clauses that behave in an intuitive fashion. The complete syntax for the if statement looks like the following:

```nasm
if constant_boolean_expression1
         Text
elseif constant_boolean_expression2
         Text
else
         Text
endif 
```

#### Repetitive Assembly

* MASM’s while..endm, for..endm, and forc..endm statements provide compiletime loop constructs. The while statement tells MASM to process the same sequence of statements repetitively during assembly. This is handy for constructing data tables as well as providing a traditional looping  structure for compile-time programs.

```
while constant_boolean_expression
        Text
endm 
```

* To understand how it works, look at the program below

```nasm
; CTL while loop demonstration program

        option  casemap:none

nl          =       10
           

            .data
ary         dword   2, 3, 5, 8, 13

            include print.inc
            
            .code

            
; Here is the "asmMain" function.

            public  asmMain
asmMain     proc
            push    rbx
            push    rbp
            mov     rbp, rsp
            sub     rsp, 56         ;Shadow storage

i           =       0            
            while   i LT 5  

            mov     edx, i          ;This is a constant!
            mov     r8d, ary[ i*4 ] ;Index is a constant
            call    print
            byte    "array[ %d ] = %d", nl, 0
              
i           =       i + 1
            endm 

             
allDone:    leave
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end
```

* MASM provides two forms of the for..endm loop. These two loops take the following general form:

```
for identifier, <arg1, arg2, ..., argn>
 .
 .
 .
endm

forc identifier, <string>
 .
 .
 .
endm 
```

* The first form of the for loop (plain for) repeats the code once for each of the arguments specified between the < and > brackets. On each repetition of the loop, it sets identifier to the text of the current argument: on the first iteration of the loop, identifier is set to arg1, and on the second iteration it is set to arg2, and so on, until the last iteration, when it is set to argn.
* The forc compile-time loop repeats the body of its loop for each character appearing in the string specified by the second argument.

## Macros

#### Standard Macros

* MASM supports a straightforward macro facility that lets you define macros in a manner that is similar to declaring a procedure. A typical, simple macro declaration takes the following form:

```
macro_name macro arguments
        Macro body
        endm
```

* To execute the code associated with a macro, you specify the macro’s name at the point you want to execute these instructions.

```nasm
mov rax, qword ptr i128
mov rdx, qword ptr i128[8]
; calling the macro
macro_name
```

* The difference between the macro invocation versus the procedure call is that macros expand their text inline, whereas a procedure call emits a call to the corresponding procedure elsewhere in the text.
* You should choose macro versus procedure call based on efficiency. Macros are slightly faster than procedure calls because you don’t execute the call and corresponding ret instructions, but they can make your program larger because a macro invocation expands to the text of the macro’s body on each invocation.
* Macros have many other disadvantages over procedures. Macros cannot have local (automatic) variables, macro parameters work differently than procedure parameters, macros don’t support (runtime) recursion, and macros are a little more difficult to debug than procedures (just to name a few disadvantages). Therefore, you shouldn’t really use macros as a substitute for procedures except when performance is absolutely critical.

#### Macro Parameters

* Macro parameter declaration syntax is straightforward. You supply a list of parameter names as the operands in a macro declaration:

```nasm
neg128 macro reg64HO, reg64LO
        neg reg64HO
        neg reg64LO
        sbb reg64HO, 0
        end
```

* When you invoke a macro, you supply the actual parameters as arguments to the macro invocation:

```
neg128 rdx, rax
```

* A macro invocation of the form neg128 rdx, rax is equivalent to the following:

```
reg64HO textequ <rdx>
reg64LO textequ <rax>
        neg reg64HO
        neg reg64LO
        sbb reg64HO, 0
```

* We use the < and > brackets to treat anything inside them as a single string.

```nasm
chkError macro instr, jump, target

         instr
         jump target
         
         endm
         
         chkError <cmp eax, 0>, jnl, RangeError ; Example 1
         .
         .
         . 
```

* When passing numeric parameters that may contain compile-time expressions, surround them in the macro with parentheses.

```nasm
Echo2nTimes macro n, theStr
echoCnt = 0
        while echoCnt LT (n) * 2
```

* If you don’t have control over the macro definition (perhaps it’s part of a library module you use, and you can’t change the macro definition because doing so could break existing code), there is another solution to this problem: use the MASM % operator before the argument in the macro invocation so that the CTL interpreter evaluates the expression before expanding the parameters. For example:

```
Echo2nTimes %3 + 1, "Hello"
```

#### Optional and Required Macro Parameters

* As a general rule, MASM treats macro arguments as optional arguments. If you define a macro that specifies two arguments and invoke that argument with only one argument, MASM will not (normally) complain about the invocation. Instead, it will simply substitute the empty string for the expansion of the second argument.
* However, suppose you left off the second parameter in the neg128 macro given earlier. That would compile to a neg instruction with a missing operand and MASM would report an error

```nasm
neg128 macro arg1, arg2 ; Line 6
       
       neg arg1         ; Line 7
       neg arg2         ; Line 8
       sbb arg1, 0      ; Line 9
       endm             ; Line 10
                        ; Line 11
       neg128 rdx       ; Line 
```

* MASM solves this issue by introducing several additional conditional if statements intended for use with text operands and macro arguments.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F9A6ErNiaH2Cu4c1suu36%2Fimage.png?alt=media&#x26;token=4c4f93b3-a0c2-49c9-95d1-e664849f73be" alt=""><figcaption></figcaption></figure>

* Example use of ifb:

```nasm
neg128 macro reg64HO, reg64LO
        ifb <reg64LO>
        .err <neg128 requires 2 operands>
        endif
        neg reg64HO
        neg reg64LO
        sbb reg64HO, 0
        endm
```

* The neg128 macro complete code:

```nasm
; neg128 macro

        option  casemap:none

nl          =       10

            .const
           

neg128      macro reg64HO, reg64LO

            ifb   <reg64LO>
            .err "neg128 requires 2 operands"
            endif

            neg  reg64HO
            neg  reg64LO
            sbb  reg64HO, 0
            endm


            include print.inc
            
            .code
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbx
            push    rbp
            mov     rbp, rsp
            sub     rsp, 56         ;Shadow storage


            mov     rdx, 12345678h
            mov     rax, 90123456h
            neg128  rdx, rax
            
             
allDone:    leave
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end
```

* A different way to handle missing macro arguments is to explicitly tell MASM that an argument is required with the :req suffix on the macro definition line.

```
neg128 macro reg64HO:req, reg64LO:req
        neg reg64HO
        neg reg64LO
        sbb reg64HO, 0
        endm
```

#### Default Macro Parameter Values

* One way to handle missing macro arguments is to define default values for those arguments.

```
neg128 macro reg64HO:=<rdx>, reg64LO:=<rax>
        neg reg64HO
        neg reg64LO
        sbb reg64HO, 0
        endm
```

* The ":=" operator tells MASM to substitute the text constant to the right of the operator for the associated macro argument if an actual value is not present on the macro invocation.

#### Macros with a Variable Number of Parameters

* It is possible to tell MASM to allow a variable number of arguments in a macro invocation:

```
varParms macro varying:vararg
        Macro body
        
        endm
        .
        .
        .
        varParms 1
        varParms 1, 2
        varParms 1, 2, 3
        varParms 
```

* A macro can have, at most, one vararg parameter. If a macro has more than one parameter and also has a vararg parameter, the vararg parameter must be the last argument.

#### The Macro Expansion (&) Operator

* Inside a macro, you can use the & operator to replace a macro name (or other text symbol) with its actual value. This operator is active anywhere, even with string literals.

```nasm
expand macro parm
        byte '&parm', 0
        endm

        .data
        expand a
```

* If, for some reason, you need the string '\&parm' to be emitted within a macro (that has parm as one of its parameters), you will have to work around the expansion operator.

```
expand macro parm
        byte '&', 'parm', 0
        endm
```

#### Local Symbols in a macro

* Local macro symbols are unique to a specific invocation of a macro. You must explicitly tell MASM which symbols must be local by using the local directive:

```nasm
macro_name macro optional_parameters
        local list_of_local_names
        Macro body
        endm
        
; example

jzc macro target
        local NotTarget
        
        jnz NotTarget
        jc target
NotTarget:
        
        endm 
```

#### The exitm Directive

* The MASM exitm directive tells MASM to immediately terminate the processing of the macro. MASM will ignore any additional lines of text within the macro.
* The exitm directive is useful in a conditional assembly sequence. Perhaps after checking for the presence (or absence) of certain macro arguments, you might want to stop processing the macro to avoid additional errors from MASM

```nasm
neg128 macro reg64HO, reg64LO
        ifb <reg64LO>
        .err <neg128 requires 2 operands>
        exitm
        endif

        neg reg64HO
        neg reg64LO
        sbb reg64HO, 0
        endm
```

#### MASM Macro Function Syntax

* Today, MASM supports additional syntax that allows you to create macro functions. A MASM macro function definition looks exactly like a normal macro definition with one addition: you use an exitm directive with a textual argument to return a function result from the macro.

```nasm
; CTL while loop demonstration program

        option  casemap:none

nl          =       10           


; upperCase macro function.
;
; Converts text argument to a string, converting
; all lower-case characters to uppercase.

upperCase   macro   theString
            local   resultString, thisChar, sep
resultStr   equ     <> ;Initialize function result with ""
sep         textequ <> ;Initialize separator char with ""

            forc    curChar, theString
            
; Check to see if the character is lower case.
; Convert it to upper case if it is, otherwise
; output it to resultStr as-is. Concatenate the
; current character to the end of the result string
; (with a ", " separator, if this isn�t the first
; character appended to resultStr).

            if      ('&curChar' GE 'a') and ('&curChar' LE 'z')
resultStr   catstr  resultStr, sep, %'&curChar'-32
            else
resultStr   catstr  resultStr, sep, %'&curChar'
            endif
            
; First time through, sep is the empty string, for all
; other iterations, sep is the comma separator between
; values.

sep         textequ <, >    
            endm    ;End for
            
            exitm   <resultStr>
            endm    ;End macro
            

; Demonstratoin of the upperCase macro function
            
            .data
chars       byte    "Demonstration of upperCase "
            byte    "macro function:"
            byte    upperCase( <abcdEFG123> ), nl, 0
            
            .code
            externdef printf:proc      
            
; Here is the "asmMain" function.
        
            public  asmMain
asmMain     proc
            push    rbx
            push    rbp
            mov     rbp, rsp
            sub     rsp, 56         ;Shadow storage

            lea     rcx, chars
            call    printf
             
allDone:    leave
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end

```

* Whenever you invoke a MASM macro function, you must always follow the macro name with a pair of parentheses enclosing the macro’s arguments. Even if the macro has no arguments, an empty pair of parentheses must be present. This is how MASM differentiates standard macros and macro functions.

## Writing Compile-Time “Programs”

* The MASM compile-time language allows you to write short programs that write other programs—in particular, to automate the creation of large or complex assembly language sequences.&#x20;

#### Constructing Data Tables at Compile Time

* One common use for the compile-time language is to build ASCII character lookup tables for alphabetic case manipulation with the xlat instruction at runtime.
* Note the use of a macro as a compiletime procedure to reduce the complexity of the table-generating code.

```nasm
; Creating lookup tables with macros

        option  casemap:none

nl          =       10

            .const
fmtStr1     byte    "testString converted to UC:", nl
            byte    "%s", nl, 0
            
fmtStr2     byte    "testString converted to LC:", nl
            byte    "%s", nl, 0
            

testString  byte    "This is a test string ", nl
            byte    "Containing UPPER CASE ", nl
            byte    "and lower case chars", nl, 0
           

emitChRange macro   start, last
            local   index, resultStr
index       =       start
            while   index lt last
            byte    index
index       =       index + 1
            endm
            endm

; Lookup table that will convert lowercase
; characters to uppercase. The byte at each
; index contains the value of that index,
; except for the bytes at indexes 'a'..'z'.
; Those bytes contain the values 'A'..'Z'.
; Therefore, if a program uses an ASCII
; character's numeric value as an index
; into this table and retrieves that byte,
; it will convert the character to upper
; case.

lcToUC      equ             this byte
            emitChRange     0, 'a'
            emitChRange     'A', %'Z'+1
            emitChRange     %'z'+1, 0ffh

; As above, but this table converts upper
; case to lower case characters.
            
UCTolc      equ             this byte
            emitChRange     0, 'A'
            emitChRange     'a', %'z'+1
            emitChRange     %'Z'+1, 0ffh

            
            .data

; Store the destination strings here:

toUC        byte    256 dup (0)
TOlc        byte    256 dup (0)     

            
            .code
            externdef printf:proc  
            
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbx
            push    rdi
            push    rsi
            push    rbp
            mov     rbp, rsp
            sub     rsp, 56         ;Shadow storage
            
; Convert the characters in testString to Uppercase

            lea     rbx, lcToUC
            lea     rsi, testString
            lea     rdi, toUC
            jmp     getUC
            
toUCLp:     xlat
            mov     [rdi], al
            inc     rsi
            inc     rdi
getUC:      mov     al, [rsi]
            cmp     al, 0
            jne     toUCLp
            
; Display the converted string

            lea     rcx, fmtStr1
            lea     rdx, toUC
            call    printf
            
                    
; Convert the characters in testString to lowercase

            lea     rbx, UCTolc
            lea     rsi, testString
            lea     rdi, TOlc
            jmp     getLC
            
toLCLp:     xlat
            mov     [rdi], al
            inc     rsi
            inc     rdi
getLC:      mov     al, [rsi]
            cmp     al, 0
            jne     toLCLp
            
; Display the converted string

            lea     rcx, fmtStr2
            lea     rdx, TOlc
            call    printf
            
       
allDone:    leave
            pop     rsi
            pop     rdi
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end

```

#### Unrolling Loops

* With a small amount of extra typing plus one copy of the loop body, you can unroll a loop as many times as you please. If you simply want to repeat the same code sequence a certain number  \
  of times, unrolling the code is especially trivial. All you have to do is wrap a MASM while..endm loop around the sequence and count off the specified number of iterations.
* For example, if you wanted to print Hello World 10 times, you could encode this as follows:

```nasm
count = 0
while count LT 10
      call print
      byte "Hello World", nl, 0

count = count + 1
endm
```

#### Using Macros to Write Macros

* One advanced use of macros is to have a macro invocation create one or more new macros. If you nest a macro declaration inside another macro, invoking that (enclosing) macro will expand the enclosed macro definition and define that macro at that point.
* Below is an example of a macro writing a macro to initialize a compile-time array

```nasm
        option  casemap:none

genArray    macro   arrayName, elements
            local   index, eleName, getName
            
; Loop over each element of the array:

index       =       0
            while   index lt &elements
            
; Generate a textequ statement to define a single
; element of the array, e.g.,
;
;  aryXX = 0
;
; where "XX" is the index (0..(elements-1))

eleName     catstr  <&arrayName>,%index,< = 0>

; Expand the text just created with the catstr directive

            eleName
            
; Move on to next array index:

index       =       index + 1
            endm    ;while
            
; Create a macro function to retrieve a value from
; the array:

getName     catstr  <&arrayName>,<_get>

getName     macro   theIndex
            local   element
element     catstr  <&arrayName>,%theIndex
            exitm   <element>
            endm
            
; Create a macro to assign a value to
; an array element


setName     catstr  <&arrayName>,<_set>

setName     macro   theIndex, theValue
            local   assign
assign      catstr  <&arrayName>, %theIndex, < = >, %theValue
            assign
            endm

            endm    ;genArray

; mout-
;
;  Replacement for echo. Allows "%" operator
; in operand field to expand text symbols.

mout        macro   valToPrint
            local   cmd
cmd         catstr  <echo >, <valToPrint>
            cmd
            endm
            
	
; Create an array ("ary") with ten elements:

            genArray ary, 10

            
; Initialize each element of the array to
; its index value:

index	= 0
	while	index lt 10
	ary_set	index, index
index	=	index + 1
	endm
	
; Print out the array values:

index	= 	0
	while	index lt 10
	
value	=	ary_get(index)
	mout	ary[%index] = %value
index	=	index + 1
	endm
	
            end
```

## Simulating HLL Procedure Calls

* Macros provide a good mechanism to call procedures and functions in a high-level-like manner.

#### HLL-Like Calls with No Parameters

* Of course, the most trivial example is a call to an assembly language procedure that has no arguments at all:

```nasm
someProc macro
          call _someProc
          endm

_someProc proc
           .
           .
           .
_someProc endp
            .
            .
            .
           someProc ; Call the procedure
```

* This simple example demonstrates a couple of conventions used for calling procedures via macro invocation:
  * If the procedure and all calls to the procedure occur within the same source file, place the macro definition immediately before the procedure to make it easy to find.
  * If you would normally name the procedure someProc, change the procedure’s name to \_someProc and then use someProc as the macro name.

#### HLL-Like Calls with One or More Parameter

* Assuming you’re using the Microsoft ABI and passing the parameter in RCX, the simplest solution is something like the following:

```nasm
someProc macro parm1
         mov rcx, parm1
         call _someProc
         endm
         .
         .
         .
         someProc Parm1Value
```

* This macro works well if you’re passing a 64-bit integer by value. If the parameter is an 8-, 16-, or 32-bit value, you would swap CL, CX, or ECX for RCX in the mov instruction.
* If you’re passing the first argument by reference, you would swap an lea instruction for the mov instruction in this example. As reference parameters are always 64-bit values, the lea instruction would usually take this form:

```
lea rcx, parm1
```

* Finally, if you’re passing a real4 or real8 value as the parameter, you’d swap one of the following instructions for the mov instruction in the previous macro:

```
movss xmm0, parm1 ; Use this for real4 parameters
movsd xmm0, parm1 ; Use this for real8 parameters
```

* Expanding the macro-calling mechanism from one parameter to two or more (assuming a fixed number of parameters) is fairly easy. All you need to do is add more formal parameters and handle those arguments in your macro definition.
* Below code uses macro invocations with multiple for calls to r10ToStr, e10ToStr, and some fixed calls to printf.

<details>

<summary>HLL-Like Calls With Multiple Parameters</summary>

```nasm
; Floating-point to string conversion

        option  casemap:none

nl          =       10

            .const
fmtStr1     byte    "r10ToStr: value='%s'", nl, 0
fmtStr2     byte    "fpError: code=%I64d", nl, 0
fmtStr3     byte    "e10ToStr: value='%s'", nl, 0

r10_1       real10  1.2345678901234567
r10_2       real10  0.0000000000000001
r10_3       real10  12345678901234567.0
r10_4       real10  1234567890.1234567
r10_5       real10  994999999999999999.0

e10_1       real10  1.2345678901234567e123
e10_2       real10  1.2345678901234567e-123
e10_3       real10  1.2345678901234567e1
e10_4       real10  1.2345678901234567e-1
e10_5       real10  1.2345678901234567e10
e10_6       real10  1.2345678901234567e-10
e10_7       real10  1.2345678901234567e100
e10_8       real10  1.2345678901234567e-100
e10_9       real10  1.2345678901234567e1000
e10_0       real10  1.2345678901234567e-1000

            .data
r10str_1    byte    32 dup (0)

           
            align   4

; TenTo17 - Holds the value 1.0e+17. Used to get a floating 
;           point number to the range x.xxxxxxxxxxxxe+17

TenTo17     real10  1.0e+17
            
; PotTblN- Hold powers of ten raised to negative powers of two.
            
PotTblN     real10  1.0,
                    1.0e-1,
                    1.0e-2,
                    1.0e-4,
                    1.0e-8,
                    1.0e-16,
                    1.0e-32,
                    1.0e-64,
                    1.0e-128,
                    1.0e-256,
                    1.0e-512,
                    1.0e-1024,
                    1.0e-2048,
                    1.0e-4096                               
                                    
                                            
; PotTblP- Hold powers of ten raised to positive powers of two.
            
            align   4
PotTblP     real10  1.0,
                    1.0e+1,
                    1.0e+2,
                    1.0e+4,
                    1.0e+8,
                    1.0e+16,
                    1.0e+32,
                    1.0e+64,
                    1.0e+128,
                    1.0e+256,
                    1.0e+512,
                    1.0e+1024,
                    1.0e+2048,
                    1.0e+4096
                                    
; ExpTbl- Integer equivalents to the powers in the tables 
;         above.
            
            align   4
ExpTab      dword   0,
                    1,
                    2,
                    4,
                    8,
                    16,
                    32,
                    64,
                    128,
                    256,
                    512,
                    1024,
                    2048,
                    4096
            
	include	print.inc
            
            .code
;*************************************************************
;
; expToBuf-
;
;  Unsigned integer to buffer.
; Used to output up to 4-digit exponents.
;
; Inputs:
;
;    EAX:   Unsigned integer to convert
;    ECX:   Print width 1-4
;    RDI:   Points at buffer.
;

expToBuf    proc

expWidth    equ     <[rbp+16]>
exp         equ     <[rbp+8]>
bcd         equ     <[rbp-16]>

            push    rdx
            push    rcx     ;At [RBP+16]
            push    rax     ;At [RBP+8]
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16

; Verify exponent digit count is in the range 1-4:

            cmp     rcx, 1
            jb      badExp
            cmp     rcx, 4
            ja      badExp
            mov     rdx, rcx
            
; Verify the actual exponent will fit in the number of digits:

            cmp     rcx, 2
            jb      oneDigit
            je      twoDigits
            cmp     rcx, 3
            ja      fillZeros       ;4 digits, no error
            cmp     eax, 1000
            jae     badExp
            jmp     fillZeros
            
oneDigit:   cmp     eax, 10
            jae     badExp
            jmp     fillZeros
            
twoDigits:  cmp     eax, 100
            jae     badExp
            
            
; Fill in zeros for exponent:

fillZeros:  mov     byte ptr [rdi+rcx*1-1], '0'
            dec     ecx
            jnz     fillZeros
            
; Point RDI at the end of the buffer:

            lea     rdi, [rdi+rdx*1-1]
            mov     byte ptr [rdi+1], 0
            push    rdi                 ;Save pointer to end

; Quick test for zero to handle that special case:

            test    eax, eax
            jz      allDone
            
; The number to convert is non-zero.
; Use BCD load and store to convert
; the integer to BCD:

            fild    dword ptr exp   ;Get integer value
            fbstp   tbyte ptr bcd   ;Convert to BCD
            
; Begin by skipping over leading zeros in
; the BCD value (max 10 digits, so the most
; significant digit will be in the HO nibble
; of byte 4).

            mov     eax, bcd        ;Get exponent digits
            mov     ecx, expWidth   ;Number of total digits
            
OutputExp:  mov     dl, al
            and     dl, 0fh
            or      dl, '0'
            mov     [rdi], dl
            dec     rdi
            shr     ax, 4
            jnz     OutputExp


; Zero-terminte the string and return:
            
allDone:    pop     rdi
            leave
            pop     rax
            pop     rcx
            pop     rdx
            clc
            ret
            
badExp:     leave
            pop     rax
            pop     rcx
            pop     rdx
            mov     rax, -3
            stc
            ret
            
expToBuf    endp


;*************************************************************
;                                                                  
; FPDigits-                                                        
;                                                                  
; Used to convert a floating point number on the FPU 
; stack (ST(0)) to a string of digits.                                           
;                                                                  
; Entry Conditions:                                                
;                                                                  
; ST(0)-    80-bit number to convert.                              
; RDI-      Points at array of at least 18 bytes where FPDigits    
;           stores the output string.                      
;                                                                  
; Exit Conditions:                                                 
;                                                                  
; RDI-      Converted digits are found here.                       
; RAX-      Contains exponent of the number.                       
; CL-       Contains the sign of the mantissa (" " or "-").
;                                                                  
;*************************************************************

P10TblN     equ     <real10 ptr [r8]>
P10TblP     equ     <real10 ptr [r9]>
xTab        equ     <dword ptr [r10]>

FPDigits    proc
            push    r8
            push    r9
            push    r10

; Special case if the number is zero.
   
            ftst
            fstsw   ax
            sahf
            jnz     fpdNotZero

; The number is zero, output it as a special case.

            fstp    tbyte ptr [rdi]  ;Pop value off FPU stack.
            mov     rax, "00000000"
            mov     [rdi], rax 
            mov     [rdi+8], rax 
            mov     [rdi+16], ax
            add     rdi, 18 
            xor     edx, edx         ; Return an exponent of 0.
            mov     cl, ' '          ; Sign is positive.
            jmp     fpdDone
                    
fpdNotZero:
            
; If the number is not zero, then fix the sign of the value.
            
            mov     cl, ' '      ; Assume it's positive.
            jbe     WasPositive  ; Flags set from sahf above.
            
            fabs             ; Deal only with positive numbers.
            mov     cl, '-'  ; Set the sign return result.
                    
WasPositive:

; Get the number between one and ten so we can figure out 
; what the exponent is.  Begin by checking to see if we have 
; a positive or negative exponent.
            
            
            xor     edx, edx        ; Initialize exponent to 0.
            fld1
            fcomip  st(0), st(1)
            jbe     PosExp

; We've got a value between zero and one, exclusive, 
; at this point.  That means this number has a negative 
; exponent.  Multiply the number by an appropriate power
; of ten until we get it in the range 1..10.
                    
            mov     esi, sizeof PotTblN     ; After last element.
            mov     ecx, sizeof ExpTab      ; Ditto.
            lea     r8, PotTblN
            lea     r9, PotTblP
            lea     r10, ExpTab

CmpNegExp:
            sub     esi, 10          ; Move to previous element.
            sub     ecx, 4           ; Zeros HO bytes
            jz      test1

            fld     P10TblN[ rsi*1 ] ; Get current power of 10.
            fcomip  st(0), st(1)     ; Compare against NOS.
            jbe     CmpNegExp        ;While Table >= value.

            mov     eax, xTab[ rcx*1 ]
            test    eax, eax
            jz      didAllDigits

            sub     edx, eax
            fld     P10TblP[ rsi*1 ]
            fmulp
            jmp     CmpNegExp

; If the remainder is *exactly* 1.0, then we can branch
; on to InRange1_10, otherwise, we still have to multiply
; by 10.0 because we've overshot the mark a bit.

test1:
            fld1
            fcomip  st(0), st(1)
            je      InRange1_10

didAllDigits:
                            
; If we get to this point, then we've indexed through
; all the elements in the PotTblN and it's time to stop.

            fld     P10TblP[ 10 ]   ; 10.0
            fmulp
            dec     edx
            jmp     InRange1_10
                         
;  At this point, we've got a number that is one or greater.
;  Once again, our task is to get the value between 1 and 10.
            
PosExp:
            
            mov     esi, sizeof PotTblP ; After last element.
            mov     ecx, sizeof ExpTab  ; Ditto.
            lea     r9, PotTblP
            lea     r10, ExpTab

CmpPosExp:
            sub     esi, 10         ; Move back 1 element in
            sub     ecx, 4          ;  PotTblP and ExpTbl.
            fld     P10TblP[ rsi*1 ]
            fcomip  st(0), st(1)
            ja      CmpPosExp;
            mov     eax, xTab[ rcx*1 ]
            test    eax, eax
            jz      InRange1_10
            
            add     edx, eax
            fld     P10TblP[ rsi*1 ]
            fdivp
            jmp     CmpPosExp
                    
                            
InRange1_10:

; Okay, at this point the number is in the range 1 <= x < 10,
; Let's multiply it by 1e+18 to put the most significant digit
; into the 18th print position.  Then convert the result to
; a BCD value and store away in memory.

            
            sub     rsp, 24         ; Make room for BCD result.
            fld     TenTo17
            fmulp
            
; We need to check the floating-point result to make sure it
; is not outside the range we can legally convert to a BCD 
; value.
;
; Illegal values will be in the range:
;
;  >999,999,999,999,999,999 ..<1,000,000,000,000,000,000
;      $403a_de0b_6b3a_763f_ff01..$403a_de0b_6b3a_763f_ffff
;
; Should one of these values appear, round the result up to
;
;           $403a_de0b_6b3a_7640_0000
            
            fstp    real10 ptr [rsp]
            cmp     word ptr [rsp+8], 403ah
            jne     noRounding
            
            cmp     dword ptr [rsp+4], 0de0b6b3ah
            jne     noRounding
                    
            mov     eax, [rsp]
            cmp     eax, 763fff01h
            jb      noRounding;
            cmp     eax, 76400000h
            jae     TooBig
                            
            fld     TenTo17
            inc     edx     ;Inc exp as this is really 10^18. 
            jmp     didRound
                                    
; If we get down here, there was some problems getting the
; value in the range 1 <= x <= 10 above and we've got a value
; that is 10e+18 or slightly larger. We need to compensate for
; that here.

TooBig:
            lea     r9, PotTblP
            fld     real10 ptr [rsp]
            fld     P10TblP[ 10 ]   ; /10
            fdivp
            inc     edx             ; Adjust exp due to fdiv.
            jmp     didRound
                            
                                    
noRounding:
            fld     real10 ptr [rsp]
didRound:   
            fbstp   tbyte ptr [rsp]

                        
; The data on the stack contains 18 BCD digits.  Convert these
; to ASCII characters and store them at the destination location
; pointed at by edi.
            
            mov     ecx, 8
repeatLp:
            mov     al, byte ptr [rsp+rcx]
            shr     al, 4                   ;Always in the
            or      al, '0'                 ; range 0..9
            mov     [rdi], al
            inc     rdi
            
            mov     al, byte ptr [rsp+rcx]
            and     al, 0fh
            or      al, '0'
            mov     [rdi], al
            inc     rdi
            
            dec     ecx     
            jns     repeatLp

            add     rsp, 24         ; Remove BCD data from stack.

fpdDone:

            mov     eax, edx        ; Return exponent in EAX.
            pop     r10
            pop     r9
            pop     r8
            ret
                    
                    
FPDigits    endp

     
;***********************************************************
;                                                           
; _r10ToStr-                                                 
;                                                           
; Converts a REAL10 floating-point number to the       
; corresponding string of digits.  Note that this           
; function always emits the string using decimal            
; notation.  For scientific notation, use the e10ToBuf      
; routine.                                                  
;                                                           
; On Entry:                                                 
;                                                           
; r10-              Real10 value to convert.
;                   Passed in ST(0).                       
;                                                           
; fWidth-           Field width for the number (note that this   
;                   is an *exact* field width, not a minimum         
;                   field width).
;                   Passed in EAX (RAX).                                    
;                                                           
; decimalpts-       # of digits to display after the decimal pt.
;                   Passed in EDX (RDX). 
;                                                           
; fill-             Padding character if the number is smaller   
;                   than the specified field width.
;                   Passed in CL (RCX).                  
;                                                                            
; buffer-           _r10ToStr stores the resulting characters in  
;                   this string.
;                   Address passed in RDI.
;
; maxLength-        Maximum string length.
;                   Passed in R8d (R8).                                     
;                                                                            
; On Exit:                                                  
;                                                           
; Buffer contains the newly formatted string.  If the       
; formatted value does not fit in the width specified,      
; _r10ToStr will store "#" characters into this string.      
;                                                           
; Carry-    Clear if success, set if an exception occurs.                                                         
;           If width is larger than the maximum length of          
;           the string specified by buffer, this routine        
;           will return with the carry set and RAX=-1.                                             
;                                                           
;***********************************************************


_r10ToStr    proc


; Local variables:

fWidth      equ     <dword ptr [rbp-8]>     ;RAX: uns32
decDigits   equ     <dword ptr [rbp-16]>    ;RDX: uns32
fill        equ     <[rbp-24]>              ;CL: char
bufPtr      equ     <[rbp-32]>              ;RDI: pointer
exponent    equ     <dword ptr [rbp-40]>    ;uns32
sign        equ     <byte ptr [rbp-48]>     ;char
digits      equ     <byte ptr [rbp-128]>    ;char[80]

            push    rdi
            push    rbx
            push    rcx
            push    rdx
            push    rsi
            push    rax
            push    rbp
            mov     rbp, rsp
            sub     rsp, 128        ;128 bytes of local vars

; First, make sure the number will fit into the 
; specified string.
            
            cmp     eax, r8d 
            jae     strOverflow
            
            test    eax, eax
            jz      voor

            mov     bufPtr, rdi
            mov     qword ptr decDigits, rdx
            mov     fill, rcx
            mov     qword ptr fWidth, rax
            
;  If the width is zero, raise an exception:

            test    eax, eax
            jz      voor
            
; If the width is too big, raise an exception:

            cmp     eax, fWidth
            ja      badWidth        

; Okay, do the conversion. 
; Begin by processing the mantissa digits:
                    
            lea     rdi, digits     ; Store result here.
            call    FPDigits        ; Convert r80 to string.
            mov     exponent, eax   ; Save away exp result.
            mov     sign, cl        ; Save mantissa sign char.

; Round the string of digits to the number of significant 
; digits we want to display for this number:

            cmp     eax, 17
            jl      dontForceWidthZero

            xor     rax, rax        ; If the exp is negative, or
                                    ; too large, set width to 0.
dontForceWidthZero:
            mov     rbx, rax        ; Really just 8 bits.
            add     ebx, decDigits  ; Compute rounding position.
            cmp     ebx, 17
            jge     dontRound       ; Don't bother if a big #.


; To round the value to the number of significant digits,
; go to the digit just beyond the last one we are considering
; (eax currently contains the number of decimal positions)
; and add 5 to that digit.  Propagate any overflow into the
; remaining digit positions.
                    
                    
            inc     ebx           ; Index+1 of last sig digit.
            mov     al, digits[rbx*1] ; Get that digit.
            add     al, 5             ; Round (e.g., +0.5 ).
            cmp     al, '9'
            jbe     dontRound

            mov     digits[ rbx*1 ], '0'+10 ; Force to zero
whileDigitGT9:                              ; (see sub 10 below).
            sub     digits[ rbx*1 ], 10     ; Sub out overflow, 
            dec     ebx                     ; carry, into prev
            js      hitFirstDigit;          ; digit (until 1st
                                            ;  digit in the #).
            inc     digits[rbx*1]
            cmp     digits[rbx], '9'        ; Overflow if > '9'.
            ja      whileDigitGT9
            jmp     dontRound
            
                                    
hitFirstDigit:
                                            
; If we get to this point, then we've hit the first
; digit in the number.  So we've got to shift all
; the characters down one position in the string of
; bytes and put a "1" in the first character position.
            
            mov     ebx, 17
repeatUntilEBXeq0:
            
            mov     al, digits[rbx*1]
            mov     digits[rbx*1+1], al
            dec     ebx     
            jnz     repeatUntilEBXeq0
                    
            mov     digits, '1'
            inc     exponent    ; Because we added a digit.
                    
dontRound: 
                    
            
; Handle positive and negative exponents separately.

            mov     rdi, bufPtr ; Store the output here.
            cmp     exponent, 0
            jge     positiveExponent

; Negative exponents:
; Handle values between 0 & 1.0 here (negative exponents
; imply negative powers of ten).
;
; Compute the number's width.  Since this value is between
; 0 & 1, the width calculation is easy: it's just the
; number of decimal positions they've specified plus three
; (since we need to allow room for a leading "-0.").
                    
            
            mov     ecx, decDigits
            add     ecx, 3
            cmp     ecx, 4
            jae     minimumWidthIs4

            mov     ecx, 4  ; Minimum possible width is four.

minimumWidthIs4:
            cmp     ecx, fWidth
            ja      widthTooBig 
            
; This number will fit in the specified field width,
; so output any necessary leading pad characters.
            
            mov     al, fill
            mov     edx, fWidth
            sub     edx, ecx
            jmp     testWhileECXltWidth
            
            
whileECXltWidth:
            mov     [rdi], al
            inc     rdi
            inc     ecx
testWhileECXltWidth:
            cmp     ecx, fWidth
            jb      whileECXltWidth
                            
; Output " 0." or "-0.", depending on the sign of the number.
            
            mov     al, sign
            cmp     al, '-'
            je      isMinus
                    
            mov     al, ' '
            
isMinus:    mov     [rdi], al
            inc     rdi
            inc     edx
                    
            mov     word ptr [rdi], '.0'
            add     rdi, 2
            add     edx, 2
            
; Now output the digits after the decimal point:

            xor     ecx, ecx        ; Count the digits in ecx.
            lea     rbx, digits     ; Pointer to data to output.
                                    
; If the exponent is currently negative, or if
; we've output more than 18 significant digits,
; just output a zero character.
            
repeatUntilEDXgeWidth: 
            mov     al, '0'
            inc     exponent
            js      noMoreOutput
            
            cmp     ecx, 18
            jge     noMoreOutput
            
            mov     al, [rbx]
            inc     ebx
                    
noMoreOutput:
            mov     [rdi], al
            inc     rdi
            inc     ecx
            inc     edx
            cmp     edx, fWidth
            jb      repeatUntilEDXgeWidth
            jmp     r10BufDone


; If the number's actual width was bigger than the width
; specified by the caller, emit a sequence of '#' characters
; to denote the error.
                            
widthTooBig:
            

; The number won't fit in the specified field width,
; so fill the string with the "#" character to indicate
; an error.
                    
                    
            mov     ecx, fWidth
            mov     al, '#'
fillPound:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillPound
            jmp     r10BufDone

            
; Handle numbers with a positive exponent here.

positiveExponent:
            
; Compute # of digits to the left of the ".".
; This is given by:
;
;                   Exponent     ; # of digits to left of "."
;           +       2            ; Allow for sign and there
;                                ; is always 1 digit left of "."
;           +       decimalpts   ; Add in digits right of "."
;           +       1            ; If there is a decimal point.
            

            mov     edx, exponent   ; Digits to left of "."
            add     edx, 2          ; 1 digit + sign posn.
            cmp     decDigits, 0
            je      decPtsIs0

            add     edx, decDigits  ; Digits to right of "."
            inc     edx             ; Make room for the "."
            
decPtsIs0:
            
; Make sure the result will fit in the
; specified field width.
            
            cmp     edx, fWidth
            ja      widthTooBig
            
                    
; If the actual number of print positions
; is less than the specified field width,
; output leading pad characters here.
            
            cmp     edx, fWidth
            jae     noFillChars
            
            mov     ecx, fWidth
            sub     ecx, edx
            jz      noFillChars
            mov     al, fill
fillChars:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillChars
                    
noFillChars:
            
            
; Output the sign character.
            
            mov     al, sign
            cmp     al, '-'
            je      outputMinus;
            
            mov     al, ' '
                    
outputMinus:
            mov     [rdi], al
            inc     rdi
                    
; Okay, output the digits for the number here.
            
            
            xor     ecx, ecx        ; Counts  # of output chars.
            lea     rbx, digits     ; Ptr to digits to output.
            
            
; Calculate the number of digits to output
; before and after the decimal point.
            
            
            mov     edx, decDigits  ; Chars after "."
            add     edx, exponent   ; # chars before "."
            inc     edx             ; Always one digit before "."
            
                    
; If we've output fewer than 18 digits, go ahead
; and output the next digit.  Beyond 18 digits,
; output zeros.
            
repeatUntilEDXeq0:
            mov     al, '0'
            cmp     ecx, 18
            jnb     putChar
            
            mov     al, [rbx]
            inc     rbx

putChar:    mov     [rdi], al
            inc     rdi     
            
; If the exponent decrements down to zero,
; then output a decimal point.
            
            cmp     exponent, 0
            jne     noDecimalPt
            cmp     decDigits, 0
            je      noDecimalPt
            
            mov     al, '.'
            mov     [rdi], al
            inc     rdi
                    
noDecimalPt:
            dec     exponent      ; Count down to "." output.
            inc     ecx           ; # of digits thus far.
            dec     edx           ; Total # of digits to output.
            jnz     repeatUntilEDXeq0
                    

; Zero-terminate string and leave:
            
r10BufDone: mov     byte ptr [rdi], 0
            leave
            clc     ;No error
            jmp     popRet

badWidth:   mov     rax, -2 ;Illegal character
            jmp     ErrorExit
            
strOverflow:
            mov     rax, -3 ;String overflow
            jmp     ErrorExit

voor:       or      rax, -1 ;Range error
ErrorExit:  leave
            stc     ;Error
            mov     [rsp], rax      ;Change RAX on return
            
popRet:     pop     rax
            pop     rsi
            pop     rdx
            pop     rcx
            pop     rbx
            pop     rdi
            ret

_r10ToStr    endp



;***********************************************************
;                                                           
; _e10ToStr-                                                   
;                                                           
; Converts a REAL10 floating-point number to the       
; corresponding string of digits.  Note that this           
; function always emits the string using scientific                  
; notation, use the _r10ToStr routine for decimal notation.   
;                                                           
; On Entry:                                                 
;                                                           
;    e10-           Real10 value to convert.
;                   Passed in ST(0)                     
;                                                           
;    width-         Field width for the number (note that this   
;                   is an *exact* field width, not a minimum     
;                   field width).
;                   Passed in RAX (LO 32 bits).                                
;                                                           
;    fill-          Padding character if the number is smaller   
;                   than the specified field width.
;                   Passed in RCX.                  
;                                                                            
;    buffer-        _e10ToStr stores the resulting characters in  
;                   this buffer (passed in EDI).
;                   Passed in RDI (LO 32 bits).                 
;                                                           
;    expDigs-       Number of exponent digits (2 for real4,     
;                   3 for real8, and 4 for real10).
;                   Passed in RDX (LO 8 bits)             
;                                                                            
;
;    maxLength-     Maximum buffer size.
;                   Passed in R8.                                     
; On Exit:                                                  
;                                                           
;    Buffer contains the newly formatted string.  If the    
;    formatted value does not fit in the width specified,   
;    _e10ToStr will store "#" characters into this string.   
;                                                           
;-----------------------------------------------------------
;                                                           
; Unlike the integer to string conversions, this routine    
; always right justifies the number in the specified        
; string.  Width must be a positive number, negative        
; values are illegal (actually, they are treated as         
; *really* big positive numbers which will always raise     
; a string overflow exception.                              
;                                                           
;***********************************************************

_e10ToStr    proc

fWidth      equ     <[rbp-8]>       ;RAX
buffer      equ     <[rbp-16]>      ;RDI
expDigs     equ     <[rbp-24]>      ;RDX
rbxSave     equ     <[rbp-32]>
rcxSave     equ     <[rbp-40]>
rsiSave     equ     <[rbp-48]>
Exponent    equ     <dword ptr [rbp-52]>
MantSize    equ     <dword ptr [rbp-56]>
Sign        equ     <byte ptr [rbp-60]>
Digits      equ     <byte ptr [rbp-128]>

            push    rbp
            mov     rbp, rsp
            sub     rsp, 128
            
            cmp     eax, r8d
            jae     strOvfl
            mov     byte ptr [rdi+rax*1], 0 ; 0-terminate str
            
            
            mov     buffer, rdi
            mov     rsiSave, rsi
            mov     rcxSave, rcx
            mov     rbxSave, rbx
            mov     fWidth, rax
            mov     expDigs, rdx
            
; First, make sure the width isn't zero.
            
            test    eax, eax
            jz      voor

; Just to be on the safe side, don't allow widths greater 
; than 1024:

            cmp     eax, 1024
            ja      badWidth

; Okay, do the conversion.

            lea     rdi, Digits     ; Store result string here.
            call    FPDigits        ; Convert e80 to digit str.
            mov     Exponent, eax   ; Save away exponent result.
            mov     Sign, cl        ; Save mantissa sign char.

; Verify that there is sufficient room for the mantissa's sign,
; the decimal point, two mantissa digits, the "E", 
; and the exponent's sign.  Also add in the number of digits
; required by the exponent (2 for real4, 3 for real8, 4 for 
; real10).
;
; -1.2e+00    :real4
; -1.2e+000   :real8
; -1.2e+0000  :real10
            
            
            mov     ecx, 6          ; Char posns for above chars.
            add     ecx, expDigs    ; # of digits for the exp.
            cmp     ecx, fWidth
            jbe     goodWidth
            
; Output a sequence of "#...#" chars (to the specified width)
; if the width value is not large enough to hold the 
; conversion:

            mov     ecx, fWidth
            mov     al, '#'
            mov     rdi, buffer
fillPound:  mov     [rdi], al
            inc     rdi
            dec     ecx
            jnz     fillPound
            jmp     exit_eToBuf


; Okay, the width is sufficient to hold the number, do the
; conversion and output the string here:

goodWidth:
            
            mov     ebx, fWidth     ; Compute the # of mantissa
            sub     ebx, ecx        ; digits to display.
            add     ebx, 2          ; ECX allows for 2 mant digs.
            mov     MantSize,ebx
                    
            
; Round the number to the specified number of print positions.
; (Note: since there are a maximum of 18 significant digits,
;  don't bother with the rounding if the field width is greater
;  than 18 digits.)
            
            
            cmp     ebx, 18
            jae     noNeedToRound
                    
; To round the value to the number of significant digits,
; go to the digit just beyond the last one we are considering
; (ebx currently contains the number of decimal positions)
; and add 5 to that digit.  Propagate any overflow into the
; remaining digit positions.
            
            mov     al, Digits[rbx*1] ; Get least sig digit + 1.
            add     al, 5             ; Round (e.g., +0.5 ).
            cmp     al, '9'
            jbe     noNeedToRound
            mov     Digits[rbx*1], '9'+1
            jmp     whileDigitGT9Test
whileDigitGT9:                      

; Subtract out overflow and add the carry into the previous
; digit (unless we hit the first digit in the number).

            sub     Digits[ rbx*1 ], 10     
            dec     ebx                     
            cmp     ebx, 0                  
            jl      firstDigitInNumber      
                                                                    
            inc     Digits[rbx*1]
            jmp     whileDigitGT9Test

firstDigitInNumber:

; If we get to this point, then we've hit the first
; digit in the number.  So we've got to shift all
; the characters down one position in the string of
; bytes and put a "1" in the first character position.
            
            mov     ebx, 17
repeatUntilEBXeq0:
            
            mov     al, Digits[rbx*1]
            mov     Digits[rbx*1+1], al
            dec     ebx
            jnz     repeatUntilEBXeq0
                    
            mov     Digits, '1'
            inc     Exponent         ; Because we added a digit.
            jmp     noNeedToRound
            
                    
whileDigitGT9Test:
            cmp     Digits[rbx], '9' ; Overflow if char > '9'.
            ja      whileDigitGT9 
            
noNeedToRound:      
            
; Okay, emit the string at this point.  This is pretty easy
; since all we really need to do is copy data from the
; digits array and add an exponent (plus a few other simple chars).
            
            xor     ecx, ecx    ; Count output mantissa digits.
            mov     rdi, buffer
            xor     edx, edx    ; Count output chars.
            mov     al, Sign
            cmp     al, '-'
            je      noMinus
            
            mov     al, ' '
                    
noMinus:    mov     [rdi], al
            
; Output the first character and a following decimal point
; if there are more than two mantissa digits to output.
            
            mov     al, Digits
            mov     [rdi+1], al
            add     rdi, 2
            add     edx, 2
            inc     ecx
            cmp     ecx, MantSize
            je      noDecPt
            
            mov     al, '.'
            mov     [rdi], al
            inc     rdi
            inc     edx     
                                    
noDecPt:
            
; Output any remaining mantissa digits here.
; Note that if the caller requests the output of
; more than 18 digits, this routine will output zeros
; for the additional digits.
            
            jmp     whileECXltMantSizeTest
            
whileECXltMantSize:
            
            mov     al, '0'
            cmp     ecx, 18
            jae     justPut0

            mov     al, Digits[ rcx*1 ]
                    
justPut0:
            mov     [rdi], al
            inc     rdi
            inc     ecx
            inc     edx
            
whileECXltMantSizeTest:
            cmp     ecx, MantSize
            jb      whileECXltMantSize

; Output the exponent:
            
            mov     byte ptr [rdi], 'e'
            inc     rdi
            inc     edx
            mov     al, '+'
            cmp     Exponent, 0
            jge     noNegExp
            
            mov     al, '-'
            neg     Exponent
                                            
noNegExp:
            mov     [rdi], al
            inc     rdi
            inc     edx
            
            mov     eax, Exponent
            mov     ecx, expDigs
            call    expToBuf
            jc      error
                    
exit_eToBuf:
            mov     rsi, rsiSave
            mov     rcx, rcxSave
            mov     rbx, rbxSave
            mov     rax, fWidth
            mov     rdx, expDigs
            leave
            clc
            ret

strOvfl:    mov     rax, -3
            jmp     error

badWidth:   mov     rax, -2
            jmp     error
            
voor:       mov     rax, -1
error:      mov     rsi, rsiSave
            mov     rcx, rcxSave
            mov     rbx, rbxSave
            mov     rdx, expDigs
            leave
            stc
            ret

_e10ToStr   endp

    
    
  
 
 

; r10ToStr-
;
;   Macro to create a HLL-like call for the 
; _r10ToStr procedure.
;
; Parameters:
;
;   r10:    Must be the name of a real4, real8, or 
;           real10 variable
;   dest:   Must be the name of a byte buffer to hold 
;           string result.
;
;   wdth:   Output width for the string. Either an
;           integer constant or a dword variable.
;
;   dPts:   Number of positions after the decimal
;           point. Either an integer constant or
;           a dword variable.
;
;   fill:   Fill char. Either a character constant
;           or a byte variable.
;
;   mxLen:  Maximum length of output string. Either
;           an integer constant or a dword variable.                   
            
r10ToStr    macro   r10, dest, wdth, dPts, fill, mxLen
            fld     r10
            
; dest is a label associated with a string variable.

            lea     rdi, dest
            
; width is either a constant or a dword var:

            mov     eax, wdth
            
; dPts is either a constant or a dword var
; holding the number of decimal point positions:

            mov     edx, dPts
            
; Process fill character. If it's a constant, 
; directly load it into ECX (which zero-extends
; into RCX). If it's a variable, then move with
; zero extension into ECX (which also zero 
; extends into RCX).
;
; Note: Bit 2 from OPATTR is 1 if fill is 
; a constant.
            
            if      ((opattr fill) and 4) eq 4
            mov     ecx, fill
            else
            movzx   ecx, fill
            endif

; maxLength is either a constant or a dword var.
            
            mov     r8d, mxLen
            call    _r10ToStr
            endm
            

; e10ToStr-
;
;   Macro to create a HLL-like call for the 
; _e10ToStr procedure.
;
; Parameters:
;
;   e10:    Must be the name of a real4, real8, or 
;           real10 variable
;   dest:   Must be the name of a byte buffer to hold 
;           string result.
;
;   wdth:   Output width for the string. Either an
;           integer constant or a dword variable.
;
;   xDigs:  Number of exponent digits.
;
;   fill:   Fill char. Either a character constant
;           or a byte variable.
;
;   mxLen:  Maximum length of output string. Either
;           an integer constant or a dword variable.                   

            

            
e10ToStr    macro   e10, dest, wdth, xDigs, fill, mxLen
            fld     e10
            
; dest is a label associated with a string variable.

            lea     rdi, dest
            
; width is either a constant or a dword var:

            mov     eax, wdth
            
; xDigs is either a constant or a dword var
; holding the number of decimal point positions:

            mov     edx, xDigs
            
; Process fill character. If it's a constant, 
; directly load it into ECX (which zero-extends
; into RCX). If it's a variable, then move with
; zero extension into ECX (which also zero 
; extends into RCX).
;
; Note: Bit 2 from OPATTR is 1 if fill is 
; a constant.
            
            if      ((opattr fill) and 4) eq 4
            mov     ecx, fill
            else
            movzx   ecx, fill
            endif

; maxLength is either a constant or a dword var.
            
            mov     r8d, mxLen
            call    _e10ToStr
            endm
            
            
; puts-
;
;   A macro to print a string using printf.
;
; Parameters:
;
;   fmt:    Format string (must be a byte
;           variable or string constant).
;
;   theStr: String to print (must be a
;           byte variable, a register,
;           or string constant).

puts        macro   fmt, theStr
            local   strConst, bool
            
            lea     rcx, fmt
            
            if      ((opattr theStr) and 2)
            
; If memory operand:

            lea     rdx, theStr
            
            elseif  ((opattr theStr) and 10h)
            
; If register operand:

            mov     rdx, theStr
            
            else 
            
; Assume it must be a string constant.

            .data
strConst    byte    theStr, 0
            .code
            lea     rdx, strConst
            
            endif
            
            call    printf
            endm

        
            public  asmMain
asmMain     proc
            push    rbx
            push    rsi
            push    rdi
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64         ;Shadow storage
            
; F output

            r10ToStr r10_1, r10str_1, 30, 16, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 15, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 14, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 13, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 12, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 11, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 10, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 9, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 8, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 7, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 6, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 5, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 4, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 3, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 2, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 1, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
            r10ToStr r10_1, r10str_1, 30, 0, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            


            r10ToStr r10_2, r10str_1, 30, 16, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            

            r10ToStr r10_3, r10str_1, 30, 1, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            

            r10ToStr r10_4, r10str_1, 30, 6, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            

            r10ToStr r10_5, r10str_1, 30, 1, '*', 32
            jc      fpError
            puts    fmtStr1, r10str_1
            
; E output

            e10ToStr e10_1, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_2, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_3, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_4, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_5, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_6, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_7, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_8, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_9, r10str_1, 26, 4, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_0, r10str_1, 26, 4, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            

            e10ToStr e10_3, r10str_1, 26, 1, '*', 32
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 26, 2, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_3, r10str_1, 26, 3, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1

            e10ToStr e10_3, r10str_1, 26, 4, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 25, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 20, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 15, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 10, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 9, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 8, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 7, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            e10ToStr e10_3, r10str_1, 6, 1, '*', 32
            jc      fpError
            puts    fmtStr3, r10str_1
            
            jmp     allDone
            
fpError:    puts    fmtStr2, rax
 

             
allDone:    leave
            pop     rdi
            pop     rsi
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end
```

</details>

#### HLL-Like Calls with a Varying Parameter List

* It can be done as shown below-

```nasm
; HLL-like procedure calls with
; a varying parameter list.

        option  casemap:none

nl          =       10

            .const
ttlStr      byte    "Listing 14-9", 0
            
            .code
            externdef printf:proc

            
; Note: don't include print.inc here
; because this code uses a macro for
; print.

; print macro-
;
; HLL-like calling sequence for the _print
; function (which is, itself, a shell for
; the printf function).
;
; If print appears on a line by itself (no
; arguments), then emit a string consisting
; of a single newline character (and zero
; terminating byte). If there are one or
; more arguments, emit each argument; append
; a single zero byte after all the arguments.
;
; Examples:
;
;           print
;           print   "Hello, World!"
;           print   "Hello, World!", nl

print       macro   arg1, optArgs:vararg
            call    _print
            
            ifb     <arg1>

; If print is used byte itself, print a
; newline character:
            
            byte    nl, 0
            
            else
            
; If we have one or more arguments, then
; emit each of them:
            
            byte    arg1

            for     oa, <optArgs>
            
            byte    oa
            
            endm

; Zero-terminate the string.

            byte    0
            
            endif
            endm
 
_print      proc
            push    rax
            push    rbx
            push    rcx
            push    rdx
            push    r8
            push    r9
            push    r10
            push    r11
            
            push    rbp
            mov     rbp, rsp
            sub     rsp, 40
            and     rsp, -16
            
            mov     rcx, [rbp+72]   ;Return address
            call    printf
            
            mov     rcx, [rbp+72]
            dec     rcx
skipTo0:    inc     rcx
            cmp     byte ptr [rcx], 0
            jne     skipTo0
            inc     rcx
            mov     [rbp+72], rcx
            
            leave
            pop     r11
            pop     r10
            pop     r9
            pop     r8
            pop     rdx
            pop     rcx
            pop     rbx
            pop     rax
            ret
_print      endp
              
; Here is the "asmMain" function.

        
            public  asmMain
asmMain     proc
            push    rbx
            push    rdi
            push    rsi
            push    rbp
            mov     rbp, rsp
            sub     rsp, 56         ;Shadow storage

            print   "Hello world"
            print
            print   "Hello, World!", nl
                        

allDone:    leave
            pop     rsi
            pop     rdi
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end
```

## Advanced Macro Parameter Parsing

#### Using opattr to Determine Argument Types&#x20;

* Opattr compile-time operator returns an integer value with certain bits set based on the type of expression that follows. In particular, bit 2 will be set if the expression following is relocatable or otherwise references memory. Therefore, this bit will be set if a variable such as fmtStr appears as the argument, and it will be clear if you pass a string literal as the argument.
* It follows the following syntax-

```
opattr expression
```

* The opattr operator returns an integer value that is a bit mask specifying the opattr attributes of the associated expression. If the expression following opattr contains forward-referenced symbols or is an illegal expression, opattr returns 0.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FB5dNb8wnUvauDwwNUFO3%2Fimage.png?alt=media&#x26;token=cb9ca494-117c-4032-bcb8-47a0690e2346" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2F7E5BNFG0ifEOj2eB3w22%2Fimage.png?alt=media&#x26;token=7f7da094-573c-48cc-a614-f8fb561c451e" alt=""><figcaption><p>opattr Return Values</p></figcaption></figure>

```nasm
; OPATTR demonstration

        option  casemap:none

nl          =       10

            .const
           
fmtStr      byte    nl, "Hello, World! #2", nl, 0

            .code
            externdef printf:proc

; cprintf macro-
;
;           cprintf fmtStr
;           cprintf "Format String"

cprintf     macro   fmtStrArg
            local   fmtStr, attr, isConst
            
attr        =       OPATTR fmtStrArg
isConst     =       (attr and 4) eq 4
            if      (attr eq 0) or isConst
            .data                                              
fmtStr      byte    fmtStrArg, 0
            .code
            lea     rcx, fmtStr
            
            else
            
            lea     rcx, fmtStrArg
            
            endif
            call    printf
            endm
 
atw         =       opattr "Hello Word"
bin         =       opattr "abcdefghijklmnopqrstuvwxyz"                 
                    
            
; Here is the "asmMain" function.
        
            public  asmMain
asmMain     proc
            push    rbx
            push    rdi
            push    rsi
            push    rbp
            mov     rbp, rsp
            sub     rsp, 56         ;Shadow storage

            cprintf "Hello World!"
            cprintf fmtStr
             
allDone:    leave
            pop     rsi
            pop     rdi
            pop     rbx
            ret     ;Returns to caller
asmMain     endp
            end
```

* If, in a macro, you were to test only bit 2 to determine if the operand is a constant, you could get into trouble when bit 2 is set and you assume that it is a constant. Probably the wisest thing to do is to mask off bits 0 to 7 (or maybe just bits 0 to 6) and compare the result against an 8-bit value rather than a simple mask.

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2FHlD1EqY0p9hB3C5bAmKC%2Fimage.png?alt=media&#x26;token=565af26b-e11f-4850-8198-ef7a772c9f37" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2429440930-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fvmiq90eCUf7ZZMUGm7Qu%2Fuploads%2Fm9FhqqRpV4uR45GpC053%2Fimage.png?alt=media&#x26;token=f3635ccc-c8d4-4518-86da-401034c5eed7" alt=""><figcaption><p>8-Bit Values for opattr Results</p></figcaption></figure>
