MASM provides some terrific macro facilities that let you accomplish some amazing things. Unfortunately, accomplishing something and understanding how it was accomplished are two different things. Although MASM provides the capability, figuring out how to take advantage of it, and working around the bugs in MASM, can produce some incomprehensible code. The macros that support the UCR Standard Library certainly border on incomprehensible, if they do not cross over into that territory. The purpose of this chapter is to help make some sense of these macros.
This macro function returns true (all bits set) if the specified operand is a literal constant. It returns false (0) if it is not a literal constant.
Checking for a string constant is rather difficult with MASM. I couldn't find an easy way to compare the first character of an operand against a quote or apostrophe. I did discover a truly disgusting way to do this; I would appreciate a better solution if somebody can discover something I've missed (that is reasonable compatible with all versions of MASM 6.x).
My solution was to build the text constant <' '> or <" "> by concatenating the first character of the operand with a space followed by the first character again. The following expression does this:
@catstr(@substr(<Operand>,1,1),< >,@substr(<Operand>,1,1))
I then use the IFIDN (if identical) statement to compare this result against <' '> and <" "> for equality. If the substring above matches either of these, I assume we've got a string literal constant. If the substring I've built doesn't match either of these, I use OPATTR to see if we've got a numeric constant.
;IsConst- Checks an operand to see if it is a constant (numeric or string). ; This function returns true (0FFh) if the operand is a constant, ; it returns false (0) otherwise. You would normally use it with ; the "if" directive. IsConst macro Operand ;; Assume if we see a quote or an apostrophe that this is a string constant ;; operand. The funny syntax below is necessary because MASM doesn't handle ;; stuff like <'> or <"> too well. % ifidn <@catstr(@substr(<Operand>,1,1),< >,@substr(<Operand>,1,1))>, <' '> exitm <-1> endif % ifidn <@catstr(@substr(<Operand>,1,1),< >,@substr(<Operand>,1,1))>, <" "> exitm <-1> endif ;; If not quote or apostrophe, check it out with OPATTR. exitm <(((OPATTR Operand) and 1100b) eq 0100b)> endm
;IsNumConst- Checks an operand to see if it is a numeric constant. ; This function returns true (0FFh) if the operand is a constant, ; it returns false (0) otherwise. You would normally use it with ; the "if" directive. IsNumConst macro Operand ;; Check it out with OPATTR. exitm <(((OPATTR Operand) and 1100b) eq 0100b)> endm
;IsStrConst- Checks an operand to see if it is a strong constant. ; This function returns true (0FFh) if the operand is a constant, ; it returns false (0) otherwise. You would normally use it with ; the "if" directive. IsStrConst macro Operand ;; Assume if we see a quote or an apostrophe that this is a string constant ;; operand. The funny syntax below is necessary because MASM doesn't handle ;; stuff like <'> or <"> too well. % ifidn <@catstr(@substr(<Operand>,1,1), \ < >, \ @substr(<Operand>,1,1))>, \ <' '> exitm <-1> endif % ifidn <@catstr(@substr(<Operand>,1,1), \ < >, \ @substr(<Operand>,1,1))>, \ <" "> exitm <-1> endif ;; If not quote or apostrophe, it's not a string constant. exitm <0> endm
This macro is a wrapper around the MASM OPATTR function to make the test for a register more readable.
;IsReg- Checks to see if an operand is a register. ; This function returns the operand as a textual value if ; it is a register. It returns the empty string (blank) ; if the operand is not a register. You would normally ; use this function in an "ifb" directive. IsReg macro Operand if (((OPATTR Operand) and 10000b) eq 10000b) exitm <Operand> else exitm <> endif endm
;IsVar- Checks an operand to see if it is a variable. ; This function returns the operand as a textual value if ; it is a variable. It returns the empty string (blank) ; if the operand is not a variable. You would normally ; use this function in an "ifb" directive. IsVar macro Operand if (((OPATTR Operand) and 1010b) eq 1010b) exitm <Operand> else exitm <> endif endm
These functions return true (all bits set, or -1) if the specified operand is a variable of the specified type; they return false (0) otherwise. One would normally use them as the operand of an IF directive.
; IsByte, ; IsWord, ; IsDword- Tests its operand to see if it is a variable of the specific ; object size. IsByte macro Operand exitm <((type Operand) eq 1) and \ (((OPATTR Operand) and 1010b) eq 1010b)> endm IsWord macro Operand exitm <((type Operand) eq 2) and \ (((OPATTR Operand) and 1010b) eq 1010b)> endm IsDword macro Operand exitm <((type Operand) eq 4) and \ (((OPATTR Operand) and 1010b) eq 1010b)> endm
These two macros begin by checking to see if the "[" and "]" characters surround the operand. After verifying this, these macros call the IsWord or IsDWord macros, passing in the characters between the "[" and "]" to determine if we've got word pointer or dword pointer syntax. If this is the case, these macros return the substring between the "[" and "]" characters (otherwise these functions return an empty string).
; IsWordPtr- Checks the operand to see if it is a word variable ; surrounded by square brackets. ; ; This function returns the operand minus the square brackets ; if it is indeed a word ptr operand. It returns blank ; otherwise. You would normally use this function in an ; "ifb" directive or in the operand field of an instruction. IsWordPtr macro Operand % ifidn <@substr(<Operand>,1,1)>, <[> % ifidn <@substr(<Operand>,@SizeStr(<Operand>),1)>, <]> % if IsWord(<@substr(<Operand>,2,@SizeStr(<Operand>)-2)>) % exitm <@substr(<Operand>,2,@SizeStr(<Operand>)-2)> endif endif endif exitm <> endm ; IsDwordPtr- Checks the operand to see if it is a dword variable ; surrounded by square brackets. ; ; This function returns the operand minus the square brackets ; if it is indeed a dword ptr operand. It returns blank ; otherwise. You would normally use this function in an ; "ifb" directive or in the operand field of an instruction. IsDwordPtr macro Operand % ifidn <@substr(<Operand>,1,1)>, <[> % ifidn <@substr(<Operand>,@SizeStr(Operand),1)>, <]> % if IsDword(@substr(<Operand>,2,@SizeStr(<Operand>)-2)) % exitm <@substr(<Operand>,2,@SizeStr(<Operand>)-2)> endif endif endif exitm <> endm
The implementation of this macro is fairly straight-forward. It simply compares its operand against the eight eight-bit register names (ignoring case) and returns the corresponding register as the result if a match occurs.
; IsReg8- Checks the parameter to see if it corresponds to a ; valid eight-bit 80x86 register. It returns that ; register as an operand if it is a valid 8-bit reg. ; It returns blank otherwise. IsReg8 macro reg8 ifidni <reg8>, <al> exitm <al> endif ifidni <reg8>, <bl> exitm <bl> endif ifidni <reg8>, <cl> exitm <cl> endif ifidni <reg8>, <dl> exitm <dl> endif ifidni <reg8>, <ah> exitm <ah> endif ifidni <reg8>, <bh> exitm <bh> endif ifidni <reg8>, <ch> exitm <ch> endif ifidni <reg8>, <dh> exitm <dh> endif exitm <> ;; Not a valid eight-bit register. endm
Note that this macro is a true macro, not a macro function. It does not return any value(s) to test with a conditional directive.
; pReg8- If the specified operand is an eight-bit register, ; this macro will push it onto the stack (it actually ; pushes 16 bits, the specified value occupies the ; L.O. byte of the value pushed). pReg8 macro Operand1 ifidni <Operand1>, <al> push ax endif ifidni <Operand1>, <bl> push bx endif ifidni <Operand1>, <cl> push cx endif ifidni <Operand1>, <dl> push dx endif ifidni <Operand1>, <ah> xchg al, ah push ax xchg al, ah endif ifidni <Operand1>, <bh> xchg bl, bh push bx xchg bl, bh endif ifidni <Operand1>, <ch> xchg cl, ch push cx xchg cl, ch endif ifidni <Operand1>, <dh> xchg dl, dh push dx xchg dl, dh endif endm
;IsReg16- Checks the parameter to see if it corresponds to a ; valid 16-bit register. IsReg16 macro reg16 ifidni <reg16>, <ax> exitm <ax> endif ifidni <reg16>, <bx> exitm <bx> endif ifidni <reg16>, <cx> exitm <cx> endif ifidni <reg16>, <dx> exitm <dx> endif ifidni <reg16>, <si> exitm <si> endif ifidni <reg16>, <di> exitm <di> endif ifidni <reg16>, <bp> exitm <bp> endif ifidni <reg16>, <sp> exitm <sp> endif exitm <> ;; Not a valid 16-bit register. endm
;IsReg32- Checks the parameter to see if it corresponds to a ; valid 32-bit register. IsReg32 macro reg32 ifidni <reg32>, <eax> exitm <eax> endif ifidni <reg32>, <ebx> exitm <ebx> endif ifidni <reg32>, <ecx> exitm <ecx> endif ifidni <reg32>, <edx> exitm <edx> endif ifidni <reg32>, <esi> exitm <esi> endif ifidni <reg32>, <edi> exitm <edi> endif ifidni <reg32>, <ebp> exitm <ebp> endif ifidni <reg32>, <esp> exitm <esp> endif exitm <> ;; Not a valid 32-bit register. endm
< 6 : 3 : 2 : >
The $Push macro pushes a value onto a stack. This macro requires two operands: the name of the stack on which to push the value (a MASM symbol) and a numeric value to push. This function pushes the value by appending the current stack value to the text < value : >.
This is a true macro, not a macro function. Therefore, you would never call it from the operand field of some other instruction or diretive.
; $Push Symstk, Value ; ; Symstk is a symbol name (it need not already exist). ; Value is a numeric value. ; $Push "pushes" this value onto the stack named Symstk by ; concatenating the string equivalent of the value to the ; begining of the Symstk symbol as a text value. Note that ; $push separates stack entries (which must be numbers) using ; the ":" symbol. $push macro SymStk, value ifnb <value> SymStk catstr <value>, <:>, SymStk endif endm
; $Pop- extracts the top of stack value (a numeric value) and equates ; the dest symbol to this value. $Pop removes the item on the top ; of the stack from the Symstk text value (everything up to and including ; the ":" stack separator). $pop macro SymStk, dest local posn posn instr 1,SymStk,<:> ifb SymStk echo Error- Empty stack (POP) err exitm endif if posn ne 0 dest substr SymStk, 1, posn-1 else dest textequ <> endif if posn ne @sizestr(%&SymStk&) SymStk substr SymStk, posn+1 else SymStk textequ <> endif endm
; $Peek is like $Pop except it does not remove the item from ; the top of the stack. $peek macro SymStk, dest local posn posn instr 1,SymStk,<:> ifb SymStk echo Error- Empty stack (PEEK) err exitm endif if posn ne 0 dest substr SymStk, 1, posn-1 else dest textequ <> endif endm
xtrn IsAlpha, TOS, STK, CS
Although the xtrn macro is intended primarily for the Standard Library routines, you can use it if you want to create your own library routines that use the same calling sequence and linkages as the Standard Library functions.
; xtrn- Generates a set of externdef statements for a given ; label. The first parameter specifies the name of the ; symbol, the remaining parameters provide the suffixes. xtrn macro name, suffixes:vararg externdef $&name&:far ifnb <suffixes> for suffix,<suffixes> externdef $&name&&suffix&:far &name&&suffix& textequ <call $&name&&suffix&> endm else &name& textequ <call $&name&> endif endm
The primary purpose of the bCSStkTOS macro is to call an appropriate Standard Library function and automatically handle the plain, register, constant, and pointer addressing modes. For example, consider the IsAlpha Standard Library function. The Standard Library supports the following variations of IsAlpha:
IsAlpha IsAlphaTOS IsAlphaSTK IsAlphaCSA corresponding invocation of the xtrn macro generates the macros for these calls, you could call each of these functions using the following syntax:
mov al, CharToTest IsAlpha push word ptr CharToTest IsAlphaTOS pshadrs CharToTest IsAlphaStk IsAlphaCS dword CharToTestNote, however, that the Standard Library provides additional syntax allowing a more convenient use of the IsAlpha set of routines. Specifically, you can supply an operand to IsAlpha as follows:
IsAlpha IsAlpha bl ;Or any 8-bit register IsAlpha 'a' ;Or any 8-bit constant IsAlpha CharVar IsAlpha [wordvar] IsAlpha [dwordvar]The first example above is the standard call to IsAlpha. The second and third examples above actually push their operand onto the stack and call IsAlphaTOS. The third example above (charvar) winds up calling the IsAlphaCS routine. The last two examples above push the 32-bit address specified (DS assumed for word variables) onto the stack and call IsAlphaSTK.
The bCSStkTOS macro handles processing the IsAlpha (and other routines') operand field to determine which routine it should actually call and how it should pass the parameters. Basically, it uses the following algorithm:
; bCSStkTOS: ; ; Routines that have a byte operand and support the plain, CS, ; Stk, and TOS addressing modes. bCSStkTOS macro funcName, Operand ifb <Operand> call $&funcName& exitm endif ;; See if it's an eight-bit register ifnb IsReg8( <Operand> ) pReg8 <Operand> call $&funcName&TOS exitm endif ;; See if it's a numeric constant if IsNumConst( <Operand> ) push Operand call $&funcName&TOS exitm endif ;; See if it's a byte variable: if IsByte( <Operand> ) call $&funcName&CS dword Operand exitm endif ;; See if it's a near or far pointer variable. ifnb IsWordPtr(<Operand>) push ds push IsWordPtr(<Operand>) call $&funcName&Stk exitm endif ifnb IsDwordPtr(<Operand>) pushd IsDwordPtr(<Operand>) call $&funcName&Stk exitm endif ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
; bCSStk: ; ; Input routines that have a byte operand and support the plain, CS, ; and Stk addressing modes. bCSStk macro funcName, Operand ifb <Operand> call $&funcName& exitm endif ;; See if it's a byte variable: if IsByte( <Operand> ) call $&funcName&CS dword Operand exitm endif ;; See if it's a near or far pointer variable. ifnb IsWordPtr(<Operand>) push ds push IsWordPtr(<Operand>) call $&funcName&Stk exitm endif ifnb IsDwordPtr(<Operand>) pushd IsDwordPtr(<Operand>) call $&funcName&Stk exitm endif ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
The primary purpose of the wCSStkTOS macro is to call an appropriate Standard Library function and automatically handle the plain, register, constant, and pointer addressing modes. For example, consider the puti Standard Library function. The Standard Library supports the following variations of puti:
puti putiTOS putiSTK putiCSA corresponding invocation of the xtrn macro generates the macros for these calls, you could call each of these functions using the following syntax:
mov ax, IntToPrint puti push IntToPrint putiTOS pshadrs IntToPrint putiStk puti dword IntToPrintNote, however, that the Standard Library provides additional syntax allowing a more convenient use of the puti set of routines. Specifically, you can supply an operand to puti as follows:
puti puti bx ;Or any 16-bit register puti 125 ;Or any 16-bit constant puti IntToPrint puti [wordvar] puti [dwordvar]The first example above is the standard call to puti. The second and third examples above actually push their operand onto the stack and call putiTOS. The third example above (IntToPrint) winds up calling the putiCS routine. The last two examples above push the 32-bit address specified (DS assumed for word variables) onto the stack and call putiSTK.
The wCSStkTOS macro handles processing the puti (and other routines') operand field to determine which routine it should actually call and how it should pass the parameters. Basically, it uses the following algorithm:
; wCSStkTOS: ; ; Routines that have a word operand and support the plain, CS, ; Stk, and TOS addressing modes. wCSStkTOS macro funcName, Operand ifb <Operand> call $&funcName& exitm endif ;; See if it's a 16-bit register ifnb IsReg16( <Operand> ) push IsReg16( <Operand> ) call $&funcName&TOS exitm endif ;; See if it's a numeric constant if IsNumConst( <Operand> ) push Operand call $&funcName&TOS exitm endif ;; See if it's a near or far pointer variable. ifnb IsWordPtr(<Operand>) push ds push IsWordPtr(<Operand>) call $&funcName&Stk exitm endif ifnb IsDwordPtr(<Operand>) pushd IsDwordPtr(<Operand>) call $&funcName&Stk exitm endif ;; See if it's a word variable: if IsWord( <Operand> ) call $&funcName&CS dword Operand exitm endif ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
; wCSStk: ; ; Input routines that have a word operand and support the plain, CS, ; and Stk addressing modes, returning a 16-bit value. wCSStk macro funcName, Operand ifb <Operand> call $&funcName& exitm endif ;; See if it's a 16-bit register (need to implement TOS mode ;; for getc, etc.) ;; ;; ifnb IsReg16( <Operand> ) ;; ;; call $&funcName&TOS ;; pop IsReg16( <Operand> ) ;; exitm ;; ;; endif ;; See if it's a near or far pointer variable. ifnb IsWordPtr(<Operand>) push ds push IsWordPtr(<Operand>) call $&funcName&Stk exitm endif ifnb IsDwordPtr(<Operand>) pushd IsDwordPtr(<Operand>) call $&funcName&Stk exitm endif ;; See if it's a word variable: if IsWord( <Operand> ) call $&funcName&CS dword Operand exitm endif ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm ; LCSStkTOS: ; ; Routines that have a dword operand and support the plain, CS, ; Stk, and TOS addressing modes.
The primary purpose of the LCSStkTOS macro is to call an appropriate Standard Library function and automatically handle the plain, register, constant, and pointer addressing modes. For example, consider the putl Standard Library function. The Standard Library supports the following variations of putl:
putl putlTOS putlSTK putlCSA corresponding invocation of the xtrn macro generates the macros for these calls, you could call each of these functions using the following syntax:
mov ax, LongToPrint putl push LongToPrint putlTOS pshadrs LongToPrint putlStk putl dword LongToPrintNote, however, that the Standard Library provides additional syntax allowing a more convenient use of the putl set of routines. Specifically, you can supply an operand to putl as follows:
putl putl ebx ;Or any 32-bit register putl 125 ;Or any 32-bit constant putl LongToPrint putl [wordvar] putl [dwordvar]The first example above is the standard call to putl. The second and third examples above actually push their operand onto the stack and call putlTOS. The third example above (LongToPrint) winds up calling the putlCS routine. The last two examples above push the 32-bit address specified (DS assumed for word variables) onto the stack and call putlSTK.
The LCSStkTOS macro handles processing the putl (and other routines') operand field to determine which routine it should actually call and how it should pass the parameters. Basically, it uses the following algorithm:
LCSStkTOS macro funcName, Operand ifb <Operand> call $&funcName& exitm endif ;; See if it's a 16-bit register ifnb IsReg32( <Operand> ) push IsReg32( <Operand> ) call $&funcName&TOS exitm endif ;; See if it's a numeric constant if IsNumConst( <Operand> ) pushd Operand call $&funcName&TOS exitm endif ;; See if it's a near or far pointer variable. ifnb IsWordPtr(<Operand>) push ds push IsWordPtr(<Operand>) call $&funcName&Stk exitm endif ifnb IsDwordPtr(<Operand>) pushd IsDwordPtr(<Operand>) call $&funcName&Stk exitm endif ;; See if it's a dword variable: if IsDWord( <Operand> ) call $&funcName&CS dword Operand exitm endif ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
; lCSStk: ; ; Input routines that have a word operand and support the plain, CS, ; and Stk addressing modes, returning a 32-bit value. lCSStk macro funcName, Operand ifb <Operand> call $&funcName& exitm endif ;; See if it's a 32-bit register (need to implement TOS mode ;; for getul, etc.) ;; ;; ifnb IsReg32( <Operand> ) ;; ;; call $&funcName&TOS ;; pop IsReg32( <Operand> ) ;; exitm ;; ;; endif ;; See if it's a near or far pointer variable. ifnb IsWordPtr(<Operand>) push ds push IsWordPtr(<Operand>) call $&funcName&Stk exitm endif ifnb IsDwordPtr(<Operand>) pushd IsDwordPtr(<Operand>) call $&funcName&Stk exitm endif ;; See if it's a dword variable: if IsDWord( <Operand> ) call $&funcName&CS dword Operand exitm endif ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
The primary purpose of the sbCSStkTOS macro is to call an appropriate Standard Library function and automatically handle the plain, register, constant, and pointer addressing modes. For example, consider the htoa Standard Library function. The Standard Library supports the following variations of htoa:
htoa htoaTOS htoaSTK htoaCSA corresponding invocation of the xtrn macro generates the macros for these calls, you could call each of these functions using the following syntax:
lesi StringVar htoa ;Leaves value in AL. pshadrs StringVar htoaTOS ;Leaves value on TOS. pshadrs StringVar ;Address of eight-bit value pshadrs HexValue htoaStk htoaCS ;Leaves value in AL. dword StringVarNote, however, that the Standard Library provides additional syntax allowing a more convenient use of the atoh set of routines. Specifically, you can supply an operand to htoa as follows:
htoa htoa HexValue, StringVar ;Uses TOS form htoa HexVar, StringVar ;Uses Stk form.The first example above is the standard call to htoa. The second example above actually pushes the value of hexValue and the address of StringVar onto the stack and calls htoaTOS. The third example above winds up calling the htoaStk routine.
The sbSStkTOS macro handles processing the htoa (and other routines') operand field to determine which routine it should actually call and how it should pass the parameters. Basically, it uses the following algorithm:
; sbCSStkTOS: ; ; Routines that have a string operand and support the plain, CS, ; Stk, and TOS addressing modes. ; ; They must produce a 8-bit result and leave the result in ; AL, TOS, or a destination byte. ; ; Note: The "ScndOpb" macro handles the second operand if it ; is present. ScndOpb macro Operand2, funcName if IsByte( <Operand2> ) push seg &Operand2& push offset &Operand2& call $&funcName&Stk exitm endif ifnb IsWordPtr( <Operand2> ) push ds push IsWordPtr( <Operand2> ) call $&funcName&Stk exitm endif ifnb IsDWordPtr( <Operand2> ) push IsDWordPtr( <Operand2> ) call $&funcName&Stk exitm endif % echo If &funcName has two operands, then second echo operand must be a byte, [word], or [dword] parameter. err endm sbCSStkTOS macro funcName, Operand1, Operand2 ifb <Operand2> ifb <Operand1> call $&funcName& exitm endif ;; See if it's a byte variable: if IsByte( <Operand1> ) call $&funcName&CS dword Operand1 exitm endif else ;Operand2 is not blank if IsByte( <Operand1> ) push seg &Operand1& push offset &Operand1& ScndOpb Operand2, funcName exitm endif ;IsByte ;; See if it's a near or far pointer variable. ifnb IsWordPtr( <Operand1> ) push ds push IsWordPtr( <Operand1> ) ScndOpb Operand2, funcName exitm endif ; Check far ptr here. ifnb IsDwordPtr(<Operand1>) pushd IsDwordPtr(<Operand1>) ScndOpb Operand2, funcName exitm endif endif ;Check of operand2. ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
swCSStkTOS macro funcName, Operand1, Operand2 ifb <Operand2> ifb <Operand1> call $&funcName& exitm endif ;; See if it's a byte variable: if IsByte( <Operand1> ) call $&funcName&CS dword Operand1 exitm endif else ;Operand2 is not blank if IsByte( <Operand1> ) push seg &Operand1& push offset &Operand1& ScndOpw Operand2, funcName exitm endif ;IsByte ;; See if it's a near or far pointer variable. ifnb IsWordPtr( <Operand1> ) push ds push IsWordPtr( <Operand1> ) ScndOpw Operand2, funcName exitm endif ; Check far ptr here. ifnb IsDwordPtr(<Operand1>) pushd IsDwordPtr(<Operand1>) ScndOpw Operand2, funcName exitm endif endif ;Check of operand2. ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm
; sdCSStkTOS: ; ; Routines that have a string operand and support the plain, CS, ; Stk, and TOS addressing modes. ; ; They must produce a 32-bit result and leave the result in ; EAX, TOS, or a destination word. ; ; Note: The "ScndOpd" macro handles the second operand if it ; is present. ScndOpd macro Operand2, funcName if IsDWord( <Operand2> ) push seg &Operand2& push offset &Operand2& call $&funcName&Stk exitm endif ifnb IsWordPtr( <Operand2> ) push ds push IsWordPtr( <Operand2> ) call $&funcName&Stk exitm endif ifnb IsDWordPtr( <Operand2> ) push IsDWordPtr( <Operand2> ) call $&funcName&Stk exitm endif % echo If &funcName has two operands, then second echo operand must be a dword, [word], or [dword] parameter. err endm sdCSStkTOS macro funcName, Operand1, Operand2 ifb <Operand2> ifb <Operand1> call $&funcName& exitm endif ;; See if it's a byte variable: if IsByte( <Operand1> ) call $&funcName&CS dword Operand1 exitm endif else ;Operand2 is not blank if IsByte( <Operand1> ) push seg &Operand1& push offset &Operand1& ScndOpd Operand2, funcName exitm endif ;IsByte ;; See if it's a near or far pointer variable. ifnb IsWordPtr( <Operand1> ) push ds push IsWordPtr( <Operand1> ) ScndOpd Operand2, funcName exitm endif ; Check far ptr here. ifnb IsDwordPtr(<Operand1>) pushd IsDwordPtr(<Operand1>) ScndOpd Operand2, funcName exitm endif endif ;Check of operand2. ;; If it's not any of the above, we have an error. % echo Illegal &funcName& operand. err endm