COMMENT $

CMACROS - assembly macros for interfacing to HLL

(C)Copyright Microsoft Cor. 1990

$

; Revision History
;	6.0	Initial Release of cmacros for MASM 6.0
;

COMMENT $

; Note:  There are some differences between this version of CMACROS
;	and previous releases.  The most signification is the fact
;	that locals and parameter names are now scoped to the body
;	of a procedure.  To help ease this problem we have introduced
;	a new directive cRet. This means that the following transformation
;	can occur on your source to deal with parameters no referenced
;	in the body of a procedure
;
;	cProc				cProc
;	locals,parms			locals,parms
;	cBegin				cBegin
;	....				...
;	cEnd				cRet
;	error code referencing 		error code referencing
;		locals and parms		locals and params
;					cEnd <nogen>
;
;	The major reason for making locals and parameters scoped was
;	the percieved benifit of error checking for defined labels in
;	the procedure and the addition of codeView information on locals
;	and parameters for functions.

$
.xcref					; Get rid of alot of symbols

;	??_out - output the given message to the console unless ?QUIET
;		has been specified
;
;	usage:
;		??_out <t>
;	where:
;		<t> is the message to output

??_out macro t
 ifndef ?QUIET
  echo t
 endif
endm

;  outif - output msg if name is non-zero.  If name is undefined,
;	set name =0, else set name to the default value.
;
;  usage:
;	outif	name, defval, onmsg, offmsg
;  where:
;	name	name of symbol
;	defval	default value to give symobl if not defined.
;			if blank, then 0 will be used
;	onmsg	text to display if symbol is non-zero
;	offmsg	text to display if symbol is zero
;

outif macro name:req, defval:=<0>, onmsg, offmsg
 ifndef name
  name = defval
 endif
 if name
  name = 1
  ifnb <onmsg>
   ??_out <! onmsg>
  endif
 else
  ifnb <offmsg>
   ??_out <! offmsg>
  endif
 endif
endm


; ??error - output message and generate an assembly time error
;
; usage:
;	??error <t>
; where:
;	t	is the text to output
;

??error macro msg
 echo e r r o r ------ msg		;; to console
 .err e r r o r ------ msg		;; forced error by assembler
endm

??_out <cMacros Version 6.00 - 1/1/91>
??_out <Copyright (C) Microsoft Corp. 1990-1991. All rights reserved.>

;
; Determine the memory model for cmacros.  Default to small if
;	no other model has been specified.
;

ifdef ?SMALL
 memS=1
endif
ifdef ?MEDIUM
 memM=1
endif
ifdef ?COMPACT
 memC=1
endif
ifdef ?LARGE
 memL=1
endif
ifdef ?HUGE
 memH=1
endif


outif	memS,0,<Small Model>
outif	memM,0,<Medium Model>
outif	memC,0,<Compact Model>
outif	memL,0,<Large Model>
outif	memH,0,<Huge Model>

memMOD = memS + memM + memL + memC + memH
if memMOD ne 1
 if memMOD eq 0
  memS=1				; Assume small model
  outif	memS,0,<Small Model>
 else
  ??error <must have only 1 memory model selected>
 endif
endif

sizec = memM + memL + memH		; Large Code models
sizeC = sizec
sized = memC + memL + memH		; Large Data models
sizeD = sized

;
;  Inform user of any other options selected
;

outif	?DF,0,<No segments or groups will be defined>
outif	?DFDATA,0,<No data segments will be defined>
outif	?DFCODE,0,<No code segments will be defined>
outif	?TF,0,<Epilogue sequences will assume valid SP>
outif	?WIN,1,<Windows support>
outif	?COW,0,<Character Windows support>
outif	?PLM,1,<PL/M calling convention>
outif	?NOATOMIC,0,<ATOMIC calling convention>
outif	?NODATA,0,<NODATA module>

ifdef ?CHKSTK
 ifdef ?CHKSTKPROC
  ??_out <! Private stack checking enabled>
 else
  ??_out <! Stack checking enabled>
 endif
else
 ?CHKSTK = 0
endif

ifndef ?DOS5
?DOS5 = 0
endif

;
; Setup some local variables to the Cmacros package
;

??CM_state = 0			; 0 - inactive, 1-cProc, 2-cBegin, 
??CM_RegSaveList textequ <>	; List of saved registers
??CM_ArgList textequ <>

;
;	This function is used to paste together two text items to
;	get a third text item.
;

??CM_Paste macro arg1:req, arg2:req
 exitm <arg1&arg2>
endm

;
;	This function is used to create a text macro containning the
;	n-th local to a fuction definition.
;

??CM_addLocal macro arg1:req
 ??CM_Paste(??CM_local, %??CM_localCount) textequ <LOCAL arg1>
 ??CM_localCount = ??CM_localCount + 1
endm

;
;	This function is used to create a text macro containning the
;	n-th parameter to a function definition
;

??CM_addParm macro arg1:req
 if ??CM_argCount EQ 20
  .err <CMACROS.INC: Cannot have more than 20 arguements to a procedure>
 endif
 ??CM_Paste(??CM_arg, %??CM_argCount) textequ <, arg1>
 ??CM_argCount = ??CM_argCount + 1
endm

;
;	This macro creates the prologue code for a cmacro defined function.
;	
;	Prologue sequences
;

cPrologue macro procname, flags, cbParms, cbLocals, reglist, userparms

  ?ba=0 				;;not in a procedure
  ?pu=0 				;;initial public setting
  ?ia=0 				;;no special prolog/epilog
  ?rp=0 				;;no register parameters
  ??CM_UserDoesFrame=0			;;don't use makeframe
  ?ff=0 				;;don't force frame setup
  ?pas=0				;;process register save list
  ?pcc=?PLM				;;calling convention (C or PL/M)

  
  
  ;;
  ;; Look at all of the user parameters and make appropriate decisions
  ;;

  for x,<userparms>
    ifdef ??CM_Paste(??_cproc_, x)
      ??CM_Paste(??_cproc_, x)
    else
      ??error <e r r o r - unknown keyword x>
      .err
    endif
  endm
  
  ;;
  ;; Now start some error checking
  ;;

  if (??CM_Atomic eq 1) and (??CM_NoData eq 0) ;;ATOMIC requires NODATA
    ??error <ATOMIC specified without NODATA - ATOMIC ignored>
    ??CM_Atomic = 0				;;clear the ATOMIC keyword
  endif

  if flags AND 020h			;;if a far procedure
    if ??CM_WinFarProc			;;if windows
      ife ??CM_Atomic			;;if not ATOMIC
        ife ?COW			;; COW dos not save DS
	  ?ia=2				;;  adjust locals for saved ds
;	  ?pas = ?pas and (not ?ds)	;;no need for extra save
	endif
      endif
    endif
  else
    ??CM_WinFarProc=0			;;not a far windows procedure
  endif

; ?pas = ?pas and (not (?sp+?cs+?ss))	;;make no sense to save these

  if ??CM_UserDoesFrame				;;don't save these if user frame
;  ?pas = ?pas and (not (?bp+?si+?di))
  endif

  if ??CM_UserDoesFrame				;;if user frame
    if ??CM_NoData
      ??error <NODATA encountered in &n - user frame ignored>
      ??CM_UserDoesFrame=0
    endif
  endif
	
  if ??CM_UserDoesFrame				;;if user frame
   if ?rp				;;If register parameters
    ??error <parmR encountered in &n - user frame ignored>
    ??CM_UserDoesFrame=0
   endif
  endif
   
  ?cpd=0				;;terminate current proc def
    
 ifidn <g>,<nogen>			;;if nogen, then cannot have locals
  if cbLocals + cbParms + ?rp	;;  saved regs, or parmRs
   ??_out <cBegin - possibly invalid use of nogen>
  endif
 else					;;else must generate a frame
   
  ;;
  ;;  Allow the user to specify his own routine which is going to
  ;;	do the frame set-up for this procedure
  ;;
  
  if ??CM_UserDoesFrame		;;if user frame code specified
   ?mf c,cbLocals,%?po		;;  call user's makeframe
   for reg,reglist		;;  save specified registers
    push reg
   endm
  else

   if ??CM_WinFarProc		;;if a far windows procedure
    ife ??CM_NoData		;;if not NODATA,
     mov ax,ds 			;;   then set AX = current ds, and    ;@
     nop			;;   leave room for  MOV AX,1234h     ;@
    endif

    ife ??CM_Atomic		;;if not ATOMIC, far frame must be set
     
     ife ?DOS5 			;;if not DOS5, then set far frame flag
      inc bp			;;  by incrementing the old bp	      ;@
     endif
	      
     push bp							      ;@
     mov bp,sp 						      ;@
	      
     ife ?COW			;; save DS not needed for CW
      push ds							      ;@
     endif
      
    else			;;ATOMIC procedure
	    
     if ?ff+cbLocals+cbParms+?rp	;;if any locals or parameters
      push bp 			;;  then must set frame pointer       ;@
      mov bp,sp			;;  to be able to access them	      ;@
     endif
	      
    endif
	    
    ife ??CM_NoData		;;if not NODATA, then AX should
     mov ds,ax 			;;  have the ds to use		      ;@
    endif
	    
   else				;;not windows.	use standard prolog
	  
    if ?ff+cbLocals+cbParms+?rp		;;if any locals or parameters
     push bp			;;  then must set frame pointer       ;@
     mov bp,sp 		;;  to be able to access them	      ;@
    endif
   endif
    
   if ?rp			;;if parmR's, push them before
    ??CM_UserDoesFrame=0			;;  allocating locals and saving
    rept ?rp			;;  the autosave registers
     uconcat mpush,,?rp,%??CM_UserDoesFrame
     ??CM_UserDoesFrame=??CM_UserDoesFrame+1
    endm
   endif
    
   if cbLocals			;;if locals to allocate
    if ?CHKSTK	 		;;if stack checking enabled
     ifdef ?CHKSTKPROC 		;;if user supplied stack checking
      ?CHKSTKPROC %cbLocals	;;  invoke it with bytes requested
     else
      mov ax,cbLocals		;;invoke default stack checking       ;@
      ife cc
       call _chkstk						      ;@
      else
       call chkstk						      ;@
      endif
     endif
    else			;;no stack checking
     sub   sp,cbLocals	;; allocate any local storage	      ;@
    endif
   endif
  endif
   
  for reg,reglist		;;save autosave registers
   push reg
  endm
 endif				;; No Gen

 ifdef ?PROFILE		;;if profiling enabled
  if c				;;  and a far procedure
   call StartNMeas		;;  invoke profile start procedure    ;@
  endif
 endif
 
 exitm %(cbLocals+?ia)
endm

;
;	This macro will define the epilogue sequences for CMACROS
;	functions.
;
;	Epilog sequences

cEpilog macro procname, flags, cbParms, cbLocals, reglist, userparms
 if ??CM_nogen			;; Nogen the cEnd --
  exitm
 endif

 ?ba=0				;;no longer in a procedure

 ifidn <g>,<nogen>		;;if nogen, then cannot have parmRs
  if a+r+lc 			;;  locals, or saved registers
   ??_out <cEnd - possibly invalid use of nogen>
  endif
 else				;;else must remove the frame
  ifdef ?PROFILE		;;if profiling enabled
   if flags AND 020H		;;  and a far procedure
    call StopNMeas		;;  invoke profile stop procedure
   endif			;;  (doesn't trash DX:AX)
  endif				;; ?PROFILE
  for reg,reglist		;;restore autosaved registers
   pop reg
  endm
  if ??CM_UserDoesFrame		;;if to use the "makeframe" procedure
   db 0c3h 			;;  near return to user's makeframe    @
  else
   if ??CM_WinFarProc		;;if far win proc, use special epilog
    ife ??CM_Atomic		;;if not ATOMIC, bp was pushed
     ife ?COW			;; restore DS not needed for CW
      if (?TF eq 0) or (cbLocals+?rp)	;;if cannot assume valid sp
       lea sp,-2+[bp]		;;  or locals or parmR's, get valid SP @
      endif
      pop ds			;;restore saved ds and bp	       @
     else
      if (?TF eq 0) or (cbLocals+?rp)	;;if cannot assume valid sp
       mov sp,bp
      endif
     endif
     pop BP			;;				       @
     ife ?DOS5			;;if not DOS5, bp was
      dec BP			;;  incremented to mark far frame      @
     endif
    else				;;ATOMIC frame was set up
     if memS32
      leave
     else
      if (?TF eq 0) or (cbLocals+?rp)	;;if cannot assume valid sp
       mov SP,BP 		;;  or locals or parmR's, get valid SP @
      endif
      if cbLocals+cbParms+?rp
       pop BP						      ;@
      endif
     endif
    endif
   else				;;non-windows standard epilog
    if ?ff+cbLocals+cbParms+?rp		;;if any parameters
     if (?TF eq 0) or (cbLocals+?rp)	;;if cannot assume valid SP
      mov SP,BP 		;;  or locals or parmR's, get valid SP;@
     endif
     pop BP							      ;@
    endif
   endif			;; Far Win Proc
   ife flags AND 010H		;;if Pascal calling convention
    ret cbParms			;;  return and remove paramteres      ;@
   else				;;else
    ret 			;;  return 			      ;@
   endif
  endif				;; User makes frame
 endif				;; noGen
endm

;
; cProc - This macro is used to define the start of a procedure in CMACROS.
;
; PARAMETERS:
;    pname   - The name of the procedure to be defined.  This field is
;		required to be present.
;    attribs - This is a list of attributes which may be placed on the
;		function being defined.
;    autoSave - This is an optional list of registers which are to be
;		saved and restored during the prologue/epilogue processing
;


cProc macro pname:REQ, attribs, autoSave

 IF ??CM_state NE 0			;; No nesting of functions allowed
  .err	<CMACROS.INC: Cannot nest procedure definitions>
 endif
 ;
 ;  Setup some state variables to start the procedure definition
 ;
 ??CM_state = 1				;; Set state variable to seen cProc
 ??CM_ProcName EQU <pname>
 ??CM_ProcAutoSave EQU <autoSave>
 ??CM_localCount = 0
 ??CM_argCount = 0
 ??CM_langType EQU <>
 ??CM_WinFarProc=?WIN			;;default far procedure (win or not)
 ??CM_NoData=?NODATA			;;default NODATA flag
 ??CM_Atomic=?NOATOMIC 			;;default is not ATOMIC
 repeat 20				;; Clear all parameter textmacros
  ??CM_Paste(??CM_arg, %??CM_argCount) textequ <>
  ??CM_argCount = ??CM_argCount + 1
 endm
 ??CM_argCount = 0
 
 ife sizec				;; Set the default distance
  dist equ <NEAR>
 else
  dist equ <FAR>
 endif
 
 vis equ <>				;; Set the default visibility
 
 for arg,<attribs>			;; Look at the attribute list
  ifidn <arg>, <FAR>
   dist equ <FAR>
  elseifidn <arg>,<NEAR>
   dist equ <NEAR>
  elseifidn <arg>,<PUBLIC>
   vis equ <PUBLIC>
  elseifidn <arg>,<PRIVATE>
   vis equ <PRIVATE>
  elseifidn <arg>,<LOCAL>		;; Ignore -- only for CRT
  elseifidn <arg>,<PASCAL>
    ??CM_langType equ <PASCAL>
  elseifidn <arg>,<C>
    ??CM_langType equ <C>
  elseifidn <arg>,<WIN>
    ??CM_WinFarProc=1
  elseifidn <arg>,<NOWIN>
    ??CM_WinFarProc=0		
  elseifidn <arg>,<NODATA>
    ??CM_NoData=1
  elseifidn <arg>,<ATOMIC>
    ??CM_Atomic=1
  else
%  .err <CMACROS.INC: cProc -- Unknown arguement '&arg'>
  endif
 endm
 
 option prologue:cPrologue		;; Change to our prologue sequence
 option epilogue:none			;; rets from here on are just rets
	
endm


;

cBegin macro pname
 local t2
IF ??CM_state NE 1			;; Must follow cProc
 .err	<CMACROS.INC: cBegin must follow a cProc>
endif
??CM_nogen = 0
ifnb	<pname>
 ifidn  <pname>,<nogen>
  ??CM_nogen = 1
 elseifdif	??CM_ProcName, <pname>
%  echo	<cBegin name (&pname) must match name on preceding cProc (&??CM_ProcName>
 endif
endif
	??CM_state = 2		;; Seen a cBegin
%	setDefLangType ??CM_langType
	macroarg EQU <>
ifnb	??CM_ProcAutoSave
	??uses CATSTR <uses >, ??CM_ProcAutoSave
	t2 = @InStr(, %??uses, <,>)
	while t2 NE 0
	    ??uses CATSTR @SubStr(<%??uses>, 1, %t2-1), < >, @SubStr(<%??uses>, %t2+1)
	    t2 = @InStr(, %??uses, <,>)
	endm
else
	??uses textequ <>
endif
ifidn defLangType,<C>
%  ??CM_Paste(_, %??CM_ProcName) textequ <??CM_ProcName>
endif

 ??CM_ProcName proc dist defLangType vis macroarg ??uses ??CM_arg0 ??CM_arg1 ??CM_arg2 ??CM_arg3 ??CM_arg4 ??CM_arg5 ??CM_arg6 ??CM_arg7 ??CM_arg8 ??CM_arg9 ??CM_arg10  ??CM_arg11 ??CM_arg12 ??CM_arg13 ??CM_arg14 ??CM_arg15 ??CM_arg16 ??CM_arg17 ??CM_arg18 ??CM_arg19
	??CM_ProcAutoSave EQU <>
t2 = 0
repeat ??CM_localCount
 ??CM_Paste(??CM_local, %t2)
 t2  = t2 + 1
endm
endm


;

cEnd macro pname
IF ??CM_state NE 2			;; Must follow a cEnd
 .err	<cEnd must follow a cProc>
endif

??CM_nogen = 0

ifnb	<pname>
 ifidn  <pname>,<nogen>
  ??CM_nogen = 1
 elseifdif ??CM_ProcName, <pname>
% echo	<cEnd name (&pname) must match preceeding cProc name (&??CM_ProcName)>
 endif
endif

 option epilogue:cEpilog
 ret
 option prologue:prologuedef
 option epilogue:epiloguedef
 ??CM_ProcName	endp
 ??CM_state = 0		;; not in a function
endm

;
;

cRet macro 
 IF ??CM_state NE 2			;; Must follow a cBegin
  .err	<cRet must follow a cProc>
 endif
 
 option epilogue:cEpilog
 ret
 option epilogue:none
endm


;
; createSeg is a macro that sets up a segment definition and
;	a logical name for that segment.  The logical name can
;	be used to egner the segment, but it cannot be used for anyting
;	else.
;
;	usage:
;		createSeg n, ln, a, co, cl, grp
;	where:
;		n	is the physical name of the segment
;		ln	is the name it is to be invoked by
;		a	is the alignment, and is optional
;		co	is the combine type, and is optional
;		cl	is the class, and is optional
;		grp	is the name of the group that contains the segment
;

createSeg macro segName, logName, aalign, combine, class, grp
  ifnb <class>
	segName segment aalign combine '&class'
  else
	segName segment aalign combine
  endif
  segName ends
  ifnb <grp>
        grp GROUP segName
	logName&OFFSET	equ	offset grp:
	logName&BASE	equ	grp
  else
	logName&OFFSET	equ	offset segName:
	logName&BASE	equ	segName
  endif

  logName&_sbegin macro
	segName	segment
	sEnd macro name
	    ifnb <name>
	     ifdifi <name>,<logName>
%	      echo <sEnd name does not match sBegin logName>
	     endif
	    endif
	    segName ends
	    purge sEnd
	endm
  endm
  
  ifnb <grp>
   logName&_assumes macro s
    assume	s:grp
   endm
  else
   logName&_assumes macro s
    assume	s:segName
   endm
  endif
endm

sBegin macro name:req
 name&_sbegin
endm

;	assumes is a macro that will set up the assumes for a segment
;	or group created with the createSeg macro.  If the assumed
;	value passed in isn't known, then a normal assume is made.
;
;       usage:
;               assumes s,g
;
;       where:
;               s       is the register to make the assumption about
;               g       is the value to assume is in it


assumes macro   s,ln
  ifndef ln&_assumes
    assume s:ln
  else
    ln&_assumes s
  endif
endm

;
; defGrp
;

defGrp macro foo:vararg
endm

; setDefLangType


setDefLangType macro overLangType
ifnb <overLangType>
 ifidn <overLangType>,<C>
  defLangType textequ <C>
 elseifidn <overLangType>,<PASCAL>
  defLangType textequ <PASCAL>
 elseifidn <overLangType>,<PLM>
  defLangType textequ <PASCAL>
 else
%  .err <Illegal Language Type specified 'overLangType'>
 endif
else ; !nb <overLangType>
 if ?PLM EQ 1
  defLangType textequ <PASCAL>
 elseif ?PLM EQ 0
  defLangType textequ <C>
 else 
  .err <Illegal value for ?PLM>
 endif
endif ; nb <overLangType>
endm


ifndef ?NOSTATIC

.xcref
.xcref staticB, staticW, staticD, staticQ, staticT, staticCP, staticDP, staticI
.cref

; staticX - define static data of type X
;
; usage:
;	staticX  n, i, s
;
; where:
;	X	is the type of the variable: b=byte, w=word, d=dword
;			q=quad word, t=ten bytes, cp=code pointer,
;			dp=data pointer, i=int
;	n	is the name of the given variable
;	i	is the initial value of the variable
;	s	is the duplication factor
;
; statics are always pascal symbols and non-public.  If they are required
;	to be public then globalX should be used.

staticB macro name:req, initVal:=<?>, repCount
 ifnb <repCount>
	name db repCount dup (initVal)
 else
	name db initVal
 endif
endm

staticW macro name:req, initVal:=<?>, repCount
 ifnb <repCount>
	name dw repCount dup (initVal)
 else
	name dw initVal
 endif
endm

staticD macro name:req, initVal:=<?>, repCount
 ifnb <repCount>
	name dd repCount dup (initVal)
 else
	name dd initVal
 endif
endm

staticI macro name:req, initVal:=<?>, repCount
 ifnb <repCount>
	name asmI repCount dup (initVal)
 else
	name asmI initVal
 endif
endm

staticQ macro name:req, initVal:=<?>, repCount
 ifnb <repCount>
	name dq repCount dup (initVal)
 else
	name dq initVal
 endif
endm

staticT macro name:req, initVal:=<?>, repCount
 ifnb <repCount>
	name dt repCount dup (initVal)
 else
	name dt initVal
 endif
endm

if sizec
 staticCP macro name:req, i, s
  staticD name,<i>,<s>
 endm
else
 staticCP macro name:req, i, s
  staticW name,<i>,<s>
 endm
endif

if sized
 staticDP macro name:req, i, s
  staticD name,<i>,<s>
 endm
else
 staticDP macro name:req, i, s
  staticW name,<i>,<s>
 endm
endif

endif ; ?NOSTATIC


globalB macro name:req, initVal:=<?>, repCount, langType
 ??CM_gbl1 name, langType, initVal, repCount, DB
endm

globalW macro name:req, initVal:=<?>, repCount, langType
 ??CM_gbl1 name, langType, <initVal>, repCount, DW
endm

globalD macro name:req, initVal:=<?>, repCount, langType
 ??CM_gbl1 name, langType, <initVal>, repCount, DD
endm

globalQ macro name:req, initVal:=<?>, repCount, langType
 ??CM_gbl1 name, langType, initVal, repCount, DQ
endm

globalT macro name:req, initVal:=<?>, repCount, langType
 ??CM_gbl1 name, langType, initVal, repCount, DT
endm

if sizec
 globalCP macro n,i,s,c
  globalD <n>,<i>,<s>,<c>
 endm
else
 globalCP macro n,i,s,c
  globalW <n>,<i>,<s>,<c>
 endm
endif

if sized
 globalDP macro n,i,s,c
  globalD <n>,<i>,<s>,<c>
 endm
else
 globalDP macro n,i,s,c
  globalW <n>,<i>,<s>,<c>
 endm
endif


??CM_gbl1 macro name, langType, initVal, repCount:=<1>, kind
 setDefLangType langType
 ifidn defLangType,<C>
  public _&name
  name textequ <_&name>
  _&name kind repCount dup (initVal)
 else
  public name
  name kind repCount dup (initVal)
 endif
endm


ifndef ?NOEXTERN
.xcref
.xcref externB, externW, externD, externQ, externT
.xcref externNP, externFP, externP, externCP, externDP, externA
.cref

; externX - define external data of type X
;
; usage:
;	externX n,c
;
; where:
;	X	is the type of the variable: b=byte, w=word, d=dword
;			q=qword, t=tbyte, cp=code pointer, dp=data pointer
;			a=absolute, i=int
;	n	is a list of names to be defined
;	c	is the lanague convention.  C for C or PASCAL or PLM for
;		pascal.  The default (?PLM flag) will be used if not specified

externA macro names:req, langtype
 ??CM_ex1 <names>, langtype, ABS
endm

externB macro names:req, langtype
 ??CM_ex1 <names>, langtype, BYTE
endm

externW macro names:req, langtype
 ??CM_ex1 <names>, langtype, WORD
endm

externD macro names:req, langtype
 ??CM_ex1 <names>, langtype, DWORD
endm

externQ macro names:req, langtype
 ??CM_ex1 <names>, langtype, QWORD
endm

externT macro names:req, langtype
 ??CM_ex1 <names>, langtype, TBYTE
endm

externNP macro names:req, langtype
 ??CM_ex1 <names>, langtype, NEAR
endm

externFP macro names:req, langtype
 ??CM_ex1 <names>, langtype, FAR
endm

if sizec
 externP macro n,c
  externFP <n>,c
 endm
 externCP macro n,c
  externD <n>,c
 endm
else
 externP macro n,c
  externNP <n>,c
 endm
 externCP macro n,c
  externW <n>,c
 endm
endif

if sized
 externDP macro n,c
  externD <n>,c
 endm
else
 externDP macro n,c
  externW <n>,c
 endm
endif

??CM_ex1 macro names, langType, kind
 setDefLangType langType
 for name,<names>
  ifidn defLangType,<C>
   name textequ ??CM_Paste(_, name)
   extern ??CM_Paste(_, name):kind
  else
   extern defLangType name:kind
  endif
 endm
endm 

endif  ; ?NOEXTERN

ifndef ?NOLABEL

; labelX - define label of data type X
;

labelB macro names:req,langType
 ??CM_lb1 <names>, langType, BYTE
endm

labelW macro names:req,langType
 ??CM_lb1 <names>, langType, WORD
endm

labelD macro names:req,langType
 ??CM_lb1 <names>, langType, DWORD
endm

labelQ macro names:req,langType
 ??CM_lb1 <names>, langType, QWORD
endm

labelT macro names:req,langType
 ??CM_lb1 <names>, langType, TBYTE
endm

labelNP macro names:req,langType
 ??CM_lb1 <names>, langType, NEAR
endm

labelFP macro names:req,langType
 ??CM_lb1 <names>, langType, FAR	
endm

if sizec
 labelP macro n,c
  labelFP <n>,c
 endm
 labelCP macro n,c
  labelD <n>,c
 endm
else
 labelP macro n,c
  labelNP <n>,c
 endm
 labelCP macro n,c
  labelW <n>,c
 endm
endif

if sized
 labelDP macro n,c
  labelD <n>,c
 endm
else
 labelDP macro n,c
  labelW <n>,c
 endm
endif

??CM_lb1 macro names:req, langType, kind
 setDefLangType langType
 ?pu = 0
 for name,<names>
  ifidn <name>,<PUBLIC>
   ?pu =1 
  else
   ifidn defLangType,<C>
    if ?pu
     public ??CM_Paste(_, name)
    endif
    name textequ ??CM_Paste(_, name)
    ??CM_Paste(_, name) label kind
   else
    if ?pu
     public name
    endif
    name label kind
   endif
  endif
 endm
endm

endif ; ?NOLABEL


ifndef ?NODEF

; defX - inform the macros that name is of type X
;
; The given name(s) is flagged to be of the given type.
;
; This macro is no longer needed.
;

for lbl,<defB, defW, defD, defQ, defT, defCP, defDP>
 lbl macro names:req
 endm
endm

endif ; ?NODEF


ifndef ?NOPTR
  ;;	regPtr generates information allowing a 32-bit pointer currently
  ;;	in a register to be pushed as a parameter to a subroutine using
  ;;	the cCall macro.
  ;;
  ;;	usage:
  ;;		regptr	n,s,o
  ;;	where:
  ;;		n	is the name the argument will be known as
  ;;		s	is the register containing the segment portion
  ;;			of the pointer
  ;;		o	is the register containing the offset portion
  ;;			of the pointer
  ;;
  ;;	2/14/85 - made obsolete with farptr

  regPtr macro n,s,o
    farPtr n,s,o
  endm



  ;;	farPtr generates information allowing a 32-bit pointer to be
  ;;	pushed as a parameter to a subroutine using the cCall macro.
  ;;
  ;;	usage:
  ;;		farptr	n,s,o
  ;;	where:
  ;;		n	is the name the argument will be known as
  ;;		s	is the segment portion of the pointer
  ;;		o	is the offset portion of the pointer
  ;;
  ;;	Note that any cast must have been made in the argument itself
  ;;	(i.e. regptr ptr1,ds,<word ptr 3[si]>)


  farPtr macro n,s,o
    n macro
      push s
      push o
    endm
  endm
endif	; ?NOPTR



;;	arg - declare arguements
;;
;;	The given arguments(s) is added to the argument list structure
;;
;;	format:
;;		arg	a
;;
;;	where:
;;		a	is any valid arugment(s) to push
;;
;;	If any element in arglist has not been defined or isn't 16-bit
;;	register, then a compilete specification must have been given in
;;	a text equate and a defx also given (if not, you'll pay the penalty!)
;;

arg macro args
 ifnb <args>
  ifnb ??CM_ArgList
   ??CM_ArgList textequ ??CM_ArgList, <,>, <args>
  else
   ??CM_ArgList textequ <args>
  endif
 endif
endm

;;	?CM_IsIdent - is the arguement a legal identifier?
;;
;;
;; Need to enforce the following additional rules:
;;	Digit may not be first character
;;	Assorted other characters may be used
;;	Dot may be the first character

?CM_IsIdent macro name:REQ
 LOCAL result
 result = 1
 forc char,<name>
  if ('A' LE '&char') AND ('&char' LE 'Z')
   goto next
  endif
  if ('a' LE '&char') AND ('&char' LE 'z')
   goto next
  endif
  if ('0' LE '&char') AND ('&char' LE '9')
   goto next
  endif
  result = 0
  exitm
:next
 endm
 exitm %result
endm


;;	@reverse
;;

@reverse macro list
 LOCAL newlist
 newlist textequ <>
 for x,<list>
  newlist catstr <x>, <,>, newlist
 endm
 ifnb newlist
  newlist substr newlist, 1, @sizestr(%newlist)-1
 endif
 exitm newlist
endm


;;	?ap - process arguments and place onto stack
;;
;;	The given argument is processed (type checking) and placed
;;	on the stack for a pending call.  There must be a type
;;	definition for all arguments (except words).  This can be
;;	done using text equates and the defx macro.
;;

?ap macro n
 ?argl=?argl+2				;; assume one word is pushed
 if 0 EQ (OPATTR n)
  if ?CM_IsIdent(n)
   ifdef n
    n
    exitm
   endif
  endif
% .err <'&n' is not valid to push>
  exitm
 else
  i = (TYPE n)
  if i EQ 1				;; byte type
   push	word ptr(n)
   exitm
  endif  

  if i EQ 2				;; word type
   push	n
   exitm
  endif

  if i EQ 4				;; dword type
   push word ptr (n)[2]
   push word ptr (n)
   ?argl=?argl+2
   exitm
  endif

  if i EQ 8				;; qword type
   push word ptr (n)[6]
   push word ptr (n)[4]
   push word ptr (n)[2]
   push word ptr (n)
   ?argl=?argl+6
   exitm
  endif
  
  push word ptr (n)			;; variable storage
  exitm
 endif
endm


;;	cCall - call a 'c' language procedure
;;
;;	The given procedure is called with the given parameters.  If the
;;	calling convention is C, the arguments are pushed in reverse order,
;;	and removed after the called procedure returns.  If the calling
;;	convention is PL/M, the arguments are pushed as they were encountered,
;;	and the called procedure is assumed to have removed them
;;	from the stack.
;;
;;	The calling convention priority will be:
;;		1) that specified on the cCall if present,
;;		2) that defined by the target,
;;		3) the default (?PLM flag).
;;
;;	format:
;;	    cCall	n,<a>,c
;;
;;	where:
;;	    n	is the name of the procedure to call
;;	    a	are the arguments to be pushed (optional, may be specified
;;		with the "arg" macro.
;;	    c	is the calling convention, C for C, PLM or PASCAL for
;;		PL/M.  The default (?PLM flag) will be used if
;;		not specified).
;;

cCall	macro name:req,args,langType

 ifnb	<args>				;; add any passed in arguments
	arg	<args>			;;   to the list of arguments
 endif					;;   for this procedure

 ifnb	??CM_RegSaveList				;; If there are any resgisters
%	for reg,<??CM_RegSaveList>			;;   to be saved across the call
		push	reg		;;   save then on the stack
	endm				;;
 endif					;;
 
 ifnb	<langType>			;; If a lang type was specified then
  setDefLangType langType		;;   it overrides all common sense
 else
  i = ((OPATTR name) SHR 8) AND 7	;; Get the lang type from the symbol
  if i EQ 0				;; No lang type ---
   setDefLangType			;;   Use the default lang type
  elseif i EQ 1				;; C lang type
   defLangType textequ <C>		;;
  elseif i EQ 4				;; Pascal Lang Type
   defLangType textequ <PASCAL>		;;
  else					;; Unknown lang type
   .err <Unknown lang type specfied for '&name'> ;;
  endif
 endif
 
 ifidn defLangType,<C>			;; If using C calling convention
  ??CM_ArgList textequ @reverse(%??CM_ArgList)	;; then reverse the arguements
 endif					;;

 ?argl=0				;; Clear arguement byte count

% for arg,<??CM_ArgList>			;; Push the procedures arguements
  ?ap <arg>				;;   onto the stack.  ?ap takes
 endm					;;   care of sizing arguements
 
 call name				;; Do the actual call
 
 ifidn defLangType,<C>			;; If this is a C proc and there
  if ?argl NE 0				;;
   add sp,?argl				;;   are parameters then pop them
  endif					;;   off the stack
 endif					;;
 
 ifnb	??CM_RegSaveList				;; If there were any saved registers
  ??CM_RegSaveList textequ @reverse(%??CM_RegSaveList)		;;   then pop them off the stack
%  for reg,<??CM_RegSaveList>			;;   at this point.
   pop	reg				;;
  endm					;;
 endif					;;
 
 ??CM_RegSaveList textequ	<>			;; Clear out the global state
 ??CM_ArgList textequ <>			;;   variable used by the cCall macro
endm



;;	save - flag that the indicated registers are to be saved/restored
;;		on the next cCall invocation
;;
;;	usage:
;;		save <r>
;;
;;	where:
;;		r is the list of registers to be saved.
;;
;;	the macro generates a value for the variable ??CM_RegSaveList
;;

save macro r
 ??CM_RegSaveList textequ <r>
endm

;;	parmX - generate reference to parameter(s) on the stack
;;
;;	usage:
;;		parmX	n
;;	where:
;;		x	is the type of the argument(s) b=byte, w=word, d=dword
;;		n	is the name(s) to be given the parmeter(s).
;;
;;	Byte are considered to be two bytes long for alignment.
;;
;;	The parmd form of the macro generates two equates:
;;
;;		off_name  -	for accessing the offset (lsw) of the parameter
;;		seg_name  -	for accessing the segment (msw) of the parameter
;;

parmB macro names:req
 for name,<names>
  ??CM_addParm <&name:BYTE>
 endm
endm

parmW macro names:req
 for name,<names>
  ??CM_addParm <&name:WORD>
 endm
endm

parmD macro names:req
 for name,<names>
  ??CM_addParm <&name:DWORD>
  ??CM_Paste(off_, name) textequ <word ptr name[0]>
  ??CM_Paste(seg_, name) textequ <word ptr name[2]>
 endm
endm

parmQ macro names:req
 for name,<names>
  ??CM_addParm <&name:QWORD>
 endm
endm

parmT macro names:req
 for name,<names>
  ??CM_addParm <&name:TBYTE>
 endm
endm

if sizec
  parmCP macro n
    parmD <n>
  endm
else
  parmCP macro n
    parmW <n>
  endm
endif

if sized
  parmDP macro n
    parmD <n>
  endm
else
  parmDP macro n
    parmW <n>
  endm
endif


if 0
;;	parmR - register parameter
;;
;;	parmR is the macro used for generating register parameters.
;;	The space allocated for the register parameters will be
;;      the ?ia (interface adjust) area which is between the  old
;;	BP and the first parameter.  Normally this is empty (?ia=0),
;;      or has the saved ds for a windows far procedure.
;;
;;	Byte and dword register parameters will be allowed.
;;
;;      usage:
;;		parmR	n,r,r2
;;      where:
;;              n       is the name of the parameter
;;              r       is the register it is in
;;              r2      is the offset register if a dword


ifndef ?NOPARMR
  .xcref
  .xcref ?pr,parmR
  .cref

  parmR macro n,r,r2
    ?pr n,r,r2,%?rp,%(?ia+2)
  endm

  ;;	?pr - register parameter
  ;;
  ;;	?pr is the actual macro for generating the equates for
  ;;	register parameters.
  ;;
  ;;	usage:
  ;;		parmR	n,r,r2,i,o
  ;;	where:
  ;;		n	is the name of the parameter
  ;;		r	is the register it is in
  ;;		r2	is the offset register if a dword
  ;;		i	is the index of the ?rp to generate
  ;;		o	is the offset from bp where the parm will be

  ?pr macro n,r,r2,i,o
    .xcref
    ifnb <r2>				;;if a dword parameter
      parmR seg_&n,r			;;define segment equate
      parmR off_&n,r2			;;define offset  equate
      n equ (dword ptr [bp-o-2])	;;define dword equate
      .xcref ?t&n
      ?t&n=4				;;show a dword to cmacros
    else
      .xcref ?rp&i
      ?rp&i=0				;;show no register(s)
      ifdef ?&r 			;;define register if valid
	?rp&i=?&r
      endif

      if ??? or (?cpd eq 0) or (?rp&i eq 0)
	??error <invalid parmR encountered: &n,&r>
	exitm
      endif

      n equ (word ptr [bp-o])		;;assume a word register
      ?t&n=2				;;show a word to cmacros
      irp x,<bh,ch,dh,bl,cl,dl,ah,al>
	if ?&&x eq ?&r			;;if really a byte register
	  n equ (byte ptr [bp-o])	;;  then make it a byte
	  ?t&n=1			;;show a byte to cmacros
	  exitm
	endif
      endm
      ?ia=?ia+2 			;;show this guy is out there
      ?rp=?rp+1 			;;show one more register parameter
    endif
    .cref
  endm
endif
endif

localB macro name
 ??CM_addLocal ??CM_Paste(name, <:BYTE>)
endm

localW macro name
 ??CM_addLocal ??CM_Paste(name, <:WORD>)
endm

localD macro name
 ??CM_addLocal ??CM_Paste(name, <:DWORD>)
  off_&name textequ <word ptr name[0]>
  seg_&name textequ <word ptr name[2]>
endm

localQ macro name
 ??CM_addLocal ??CM_Paste(name, <:QWORD>)
endm

localT macro name
 ??CM_addLocal ??CM_Paste(name, <:TBYTE>)
endm

if sizec
  localCP macro n
    localD <n>
  endm
else
  localCP macro n
    localW <n>
  endm
endif

if sized
  localDP macro n
    localD <n>
  endm
else
  localDP macro n
    localW <n>
  endm
endif

localV macro name,a
 local t1
 t1 catstr <name>, < [>, %a, <]:BYTE>
% ??CM_addLocal <t1>
endm


ife ?DF
;;
;;	Define all segments that will be used.  This will allow the
;;	assume and groups to be set up at one given place, and also
;;	allow quick changes to be made
;;

createSeg _TEXT,Code,word,public,CODE
ife ?NODATA
 createSeg _DATA,Data,word,public,DATA,DGROUP
endif
endif

;       errnz exp - generate error message if expression isn't zero
;
;	The errnz will generate an error message if the expression "exp"
;	does not evaluate to zero.  This macro is very useful for testing
;       relationships between items, labels, and data that was coded into
;       an application.
;
;                  errnz   <offset $ - offset label>   ;error if not at "label"
;                  errnz   <eofflag and 00000001b>     ;eofflag must be bit 0
;
;	For expressions involving more than one token, the angle brackets
;       must be used.
;
;	The macro is only evaluated on pass 2, so forward references may be
;       used in the expression.

errnz macro x			;;display error if expression is <>0
 .errnz x
endm

;       errn$ label,exp - generate error message if label (exp) <> $
;
;	The errnz will generate an error message if the label and "exp"
;       does not evaluate to the current value of the location counter.
;	This macro is very useful for testing relationships between
;       labels and the location counter that was coded into an application.
;
;       examples:  errn$   label        ;error if not at "label"
;                  errn$   label,+3     ;error if not three bytes from "label"
;                  errn$   label,-3     ;error if not three bytes past "label"
;
;	If no "exp" is given, it is the same as specifying 0
;
;	The macro is only evaluated on pass 2, so forward references may be
;       used in the expression.

errn$ macro l,x 			;;error if <$-label1 (exp2)> <>0
  errnz   <offset $ - offset l x>
endm

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; Extra macros for the c-runtime package
;
; Macro for calling another run-time-library function.
; Does a PUSH CS/CALL NEAR in compact/large models, except
; for QuickC.		--PHG, 5-24-89

callcrt MACRO	funcname
ifdef _QC2
	call	funcname
else
  if sizeC
	push	cs
	call	near ptr (funcname)
  else
	call	funcname
  endif
endif
ENDM

.cref					; Permit symbols to be listed again
