
;   FILENAME: IMACROS.MAC
;
;   Copyright (c) 1988, 1989 by Borland International, Inc.
;
;   DESCRIPTION: This include file implements miscellaneous useful macros.
;   The file uses ideal mode syntax.
;
;   ASSEMBLY INSTRUCTIONS: If assembling modules for 286/386 processors,
;   turn on the P286 directive in order to take advantage of 286/386 specific
;   instructions. I.E.
;
;       TASM filename /jP286
;
;   NOTE: In order to use this macro file you must also include the macro file
;   IBIOS.MAC in your module.
;
;   The macros implemented in this file are:
;   Name                Function
;   ---------------     ---------------------------------------------------
;   FarPtrAddress       Calculate the absolute address of a 32-bit pointer
;   CompareFarPointers  Compare two 32-bit pointers
;   MakePascalString    Allocate a string using Turbo Pascal format
;   InitStack           Initialize the stack registers for parameter access
;   RestoreStack        Restore the stack registers
;   GetVideoAddress     Determine the segment address of video ram
;   DosCall             Performs the DOS int 21h call
;   LoadSegment         Loads a segment register with a value
;   Beep                Sounds the speaker


;   The following macro calculates the absolute address of the far pointer
;   passed to it.
;
;   Input
;       Segment:Ofs - Far pointer
;   Output
;       dx:ax - Absolute address of pointer(20 bits)
;   Calling convention
;       NA
;   Registers modified
;       ax, bx, cx, dx, Flags

macro   FarPtrAddress Segment, Ofs
    ifb <Segment>
        display "Segment parameter must be provided in call to FarPtrAddress"
        err
    endif
    ifb <Ofs>
        display "Ofs parameter must be provided in call to FarPtrAddress"
        err
    endif

    mov     bx, Segment     ; Calculate the low 12 bits
    and     bx, 0FFFh
    if (@Cpu and 100b) eq 100b  ; Shift the segment value right 12 bits
        shl     bx, 4           ; to prepare for addition
    else
        mov     cl, 4
        shl     bx, cl
    endif
    mov     dx, Segment
    if (@Cpu and 100b) eq 100b  ; Shift the segment value right 12  bits
        shr     dx, 12          ; to prepare for addition
    else
        mov     cl, 12
        shr     dx, cl
    endif
    mov     ax, Ofs         ; Determine the low 16 bits of the 20-bit
    add     ax, bx          ; address
    adc     dx, 0           ; Calculate the absolute segment
endm


;   The following macros compares two far pointers to determine their
;   relative absolute addresses.
;
;   Input
;       Seg1:Ofs1 - Far pointer
;       Seg2:Ofs2 - Far pointer
;   Output
;       The Flags register is set by the CMP instruction.
;   Calling convention
;       NA
;   Registers modified
;       ax, bx, cx, dx, Flags

macro   CompareFarPointers    Seg1, Ofs1, Seg2, Ofs2
    local   Exit, SegmentsEqual
    ifb <Seg1>
        display "Need to declare Seg1 in call to CompareFarPointers"
        err
    endif
    ifb <Seg2>
        display "Need to declare Seg2 in call to CompareFarPointers"
        err
    endif
    ifb <Ofs1>
        display "Need to declare Ofs1 in call to CompareFarPointers"
        err
    endif
    ifb <Ofs2>
        display "Need to declare Ofs2 in call to CompareFarPointers"
        err
    endif

    mov     ax, Seg1            ; Load segment registers
    mov     dx, Seg2
    cmp     ax, dx              ; Check if segment registers are equal
    je      short SegmentsEqual

    ; If the segments aren't equal we have to calculate the absolute
    ; addresses of the pointers

    FarPtrAddress Seg1, Ofs1    ; Calculate absolute adress of Seg1:Ofs1
    push    dx                  ; Store calculated values on the stack
    push    ax
    FarPtrAddress Seg2, Ofs2    ; Calculate absolute adress of Seg2:Ofs2
    mov     bx, sp
    cmp     [ss:bx+2], dx       ; Compare absolute segment adresses
    pop     bx                  ; Clean up the stack
    pop     bx
    jne     short Exit          ; If they aren't equal we're done
    cmp     [ss:bx], ax         ; Check absolute offsets
    jmp     short Exit
SegmentsEqual:
    cmp     Ofs1, Ofs2          ; Compare offsets
Exit:
endm

;   The following macro builds strings using Turbo Pascal conventions. That
;   is, the string is stored with a preceding 'length byte.' This macro is
;   an excellent example of the clever things that can be done using the
;   ORG directive.
;
;   Input
;       Id - Label to associate with the string
;       Msg - Contents of the string
;   Output
;       none
;   Calling convention
;       NA
;   Registers modified
;       none

macro   MakePascalString    Id, Msg
    local   MsgLen, EndStr
    ifb <Id>
        display "Must pass name of string to MakePascalString"
        err
    else
        ifb <Msg>
            display "Must pass string parameter to MakePascalString
            err
        else
            Id  db  EndStr - Id - 1, &Msg&  ; Allocate space for the string
            label   EndStr  byte            ; a preceding byte storing the
        endif                               ; length of the string.
    endif
endm


;   The following macro initializes bp and sp in preparation for access
;   of parameters passed to a routine on the stack. The parameter LocalVars
;   is optional and if provided indicates the amount of space that should
;   be reserved for local variables. LocalVars must be an even value.
;
;   Input
;       LocalVars - # of bytes to reserve for local variables
;   Output
;       none
;   Calling convention
;       NA
;   Registers modified
;       bp, sp

macro   InitStack   LocalVars
    push    bp                  ; Set up the stack for a sub-routine
    mov     bp, sp
    ifnb    <LocalVars>
        sub     sp, LocalVars   ; Make space for local variables
    endif
endm


;   The following macro restores bp and sp after a routine is finished
;   accessing parameters passed to a routine on the stack. The parameter
;   LocalVars is optional and if provided indicates the amount of space
;   that was reserved for local variables. LocalVars must be an even value.
;
;   Input
;       LocalVars - # of bytes that were reserved for local variables
;   Output
;       none
;   Calling convention
;       NA
;   Registers modified
;       bp, sp

macro   RestoreStack    LocalVars
    ifnb    <LocalVars>
        add     sp, LocalVars
    endif   ; ifnb <LocalVars>
    pop     bp
endm    ; InitStack


;   The following macro returns the current segment address of video ram.
;
;   Input
;       none
;   Output
;       ax
;   Calling convention
;       NA
;   Registers modified
;       ax, bx, Flags

macro   GetVideoAddress
    local   Color, Exit
    GetVideoMode
    cmp     al, 07h
    jne     short Color
    mov     ax, 0B000h      ;; It's a monochrome card
    jmp     short Exit
Color:
    mov     ax, 0B800h      ;; It's a color card
Exit:
endm



;   The following macro performs the Int 21h call to execute DOS's
;   services.
;
;   Input
;       Service - Service # to execute
;   Output
;       None
;   Calling convention
;       NA
;   Registers modified
;       NA

macro   DosCall Service
    ifb <Service>
        display "The Service parameter must be provided to DosCall
        err
    endif
    mov ah, Service
    int DOS_FUNCTION
endm


;   The following macro loads a segment register with a value. The macro
;   attempts to do all possible checks to determine the type of the value
;   being moved into the segment register. Based on this information it
;   generates the correct code.
;
;   Input
;       Segm    - Segm register to load value into
;       Value   - Value to put on the stack
;   Output
;       none
;   Calling convention
;       NA
;   Registers modified
;       ax

macro   LoadSegment Segm, Value
    local   IsSegReg

    macro   CheckRegisterName   Register
        IsSegReg = 0
        if (symtype Register) eq 110000b
            irp Reg, <es, ds, cs, ss, fs, gs>
                ifidni  <Register>, <&Reg&>
                    IsSegReg = 1                ; Return that the register
                    exitm                       ; is a segment register
                endif
            endm
        endif
    endm

    CheckRegisterName   <Value>                 ; Check if the value to be
    if IsSegReg                                 ; loaded into the segment
        push    Value                           ; register is itself a
        pop     Segm                            ; segment register.
    else
        if (symtype Segm eq 24h) or (symtype Segm eq 0h)    ; Parameter is a constant
            mov     ax, Value
            mov     Segm, ax
        else
            mov     Segm, Value                 ; Value is a memory reference
        endif
    endif
endm



;   The following macro generates a beep by displaying the ASCII Bell
;   character.
;
;   Input
;       none
;   Output
;       none
;   Calling convention
;       NA
;   Registers modified
;       ax, dl

macro   Beep
    mov dl, BELL
    DosCall DOS_CHARACTER_OUTPUT
endm


