[list -]
%if 0

NASM macro collection
Public Domain by C. Masloch, 2008-2012
Intended for 86 Mode programs.

%endif

%ifndef __lMACROS1_MAC__
%assign __lMACROS1_MAC__ 1

%if !!2 - 1
 %error Unary operator ! returns values > 1. Adjust all sources.
%endif

%idefine by byte
%idefine wo word
%idefine dwo dword

%idefine b byte
%idefine w word
%idefine d dword

%idefine s short
%idefine sho short

%idefine n near
%idefine ne near

%idefine f far


		; Bind major and minor version byte to version word or
		; version word with exchanged bytes.
%idefine ver(major,minor)	( ((major)&0FFh)<<8|((minor)&0FFh) )
%idefine verx(major,minor)	( ((minor)&0FFh)<<8|((major)&0FFh) )

		; Macros for table creation
%define count(start,stop) stop-start+1
%define range(start,stop) (start),count(start,stop)	; generates two comma-separated values

		; Work with 4-byte dates
%define date4b(y,m,d)	(((y)&0FFFFh)<<16|((m)&0FFh)<<8|((d)&0FFh))	; create 4-byte date
%define year4bd(d)	(((d)>>16)&0FFFFh)	; get year from 4-byte date
%define month4bd(d)	(((d)>>8)&0FFh)		; get month from 4-byte date
%define day4bd(d)	((d)&0FFh)		; get day from 4-byte date
						; The 4-byte date stores the year 0-65535 in the high word,
						; followed by the month 0-255 in the high byte (of the low
						; word) and the day 0-255 in the low byte.


		; Write numbers as decimal digits into a string
%define _1digits_nocheck(d)	(((d)% 10)+'0')
%xdefine _2digits_nocheck(d)	_1digits_nocheck((d)/10),_1digits_nocheck(d)
%xdefine _3digits_nocheck(d)	_1digits_nocheck((d)/100),_2digits_nocheck(d)
%xdefine _4digits_nocheck(d)	_1digits_nocheck((d)/1000),_3digits_nocheck(d)
%xdefine _5digits_nocheck(d)	_1digits_nocheck((d)/10000),_4digits_nocheck(d)
%xdefine _1digits(d)	(!!(d/10)*(1<<32)+  _1digits_nocheck(d))
%xdefine _2digits(d)	_1digits((d)/   10),_1digits_nocheck(d)
%xdefine _3digits(d)	_1digits((d)/  100),_2digits_nocheck(d)
%xdefine _4digits(d)	_1digits((d)/ 1000),_3digits_nocheck(d)
%xdefine _5digits(d)	_1digits((d)/10000),_4digits_nocheck(d)

		; Write number as decimal digits into a string,
		; and automagically determine how many digits to use.
		; The input number is a critical expression to NASM.
		;
		; %1 = number to write
		; %2 = minimal number of digits, 0..5. defaults to 1
		;	(setting it to 0 with a number of 0 writes nothing)
%macro _autodigits 1-2.nolist 1
%if %2 > 5
 %error Minimal number of digits 6 or more: %2
%endif
%if (%1) >= 100000
 %error Number has to use 6 or more digits: %1
%elif (%1) >= 10000 || %2 == 5
	db _5digits(%1)
%elif (%1) >= 1000 || %2 == 4
	db _4digits(%1)
%elif (%1) >= 100 || %2 == 3
	db _3digits(%1)
%elif (%1) >= 10 || %2 == 2
	db _2digits(%1)
%elif (%1) >= 1 || %2 == 1
	db _1digits(%1)
%endif
%endmacro

		; %1 = name of single-line macro to set. will be prefixed by underscore
		; %2 = number to write
		; %3 = minimal number of digits, 0..5. defaults to 1
		;	(setting it to 0 with a number of 0 defines macro to "")
%macro _autodigitsdef 2-3.nolist 1
%if %3 > 5
 %error Minimal number of digits 6 or more: %3
%endif
%if (%2) >= 100000
 %error Number has to use 6 or more digits: %2
%elif (%2) >= 10000 || %3 == 5
%define _%1 %[_5digits(%2)]
%elif (%2) >= 1000 || %3 == 4
%define _%1 %[_4digits(%2)]
%elif (%2) >= 100 || %3 == 3
%define _%1 %[_3digits(%2)]
%elif (%2) >= 10 || %3 == 2
%define _%1 %[_2digits(%2)]
%elif (%2) >= 1 || %3 == 1
%define _%1 %[_1digits(%2)]
%elif
%define _%1 ""
%endif
%endmacro

		; Write numbers as hexadecimal digits into a string
%define _1digitshex_nocheck(h)	( ((h)& 0Fh) + '0' + ('A'-'9'-1)*(((h)& 0Fh)/10) )
%xdefine _2digitshex_nocheck(h)	_1digitshex_nocheck((h)>> 4),_1digitshex_nocheck(h)
%xdefine _3digitshex_nocheck(h)	_1digitshex_nocheck((h)>> 8),_2digitshex_nocheck(h)
%xdefine _4digitshex_nocheck(h)	_1digitshex_nocheck((h)>>12),_3digitshex_nocheck(h)
%xdefine _5digitshex_nocheck(h)	_1digitshex_nocheck((h)>>16),_4digitshex_nocheck(h)
%xdefine _6digitshex_nocheck(h)	_1digitshex_nocheck((h)>>20),_5digitshex_nocheck(h)
%xdefine _7digitshex_nocheck(h)	_1digitshex_nocheck((h)>>24),_6digitshex_nocheck(h)
%xdefine _8digitshex_nocheck(h)	_1digitshex_nocheck((h)>>28),_7digitshex_nocheck(h)
%xdefine _1digitshex(h)	(!!(h&~0Fh)*(1<<32)+ _1digitshex_nocheck(h))
%xdefine _2digitshex(h)	_1digitshex((h)>> 4),_1digitshex_nocheck(h)
%xdefine _3digitshex(h)	_1digitshex((h)>> 8),_2digitshex_nocheck(h)
%xdefine _4digitshex(h)	_1digitshex((h)>>12),_3digitshex_nocheck(h)
%xdefine _5digitshex(h)	_1digitshex((h)>>16),_4digitshex_nocheck(h)
%xdefine _6digitshex(h)	_1digitshex((h)>>20),_5digitshex_nocheck(h)
%xdefine _7digitshex(h)	_1digitshex((h)>>24),_6digitshex_nocheck(h)
%xdefine _8digitshex(h)	_1digitshex((h)>>28),_7digitshex_nocheck(h)


%macro _appenddigitstrdef 2.nolist
%substr %%ii "0123456789ABCDEF" (%2) + 1
%strcat _%1 _%1,%%ii
%endmacro

		; %1 = name of single-line macro to set. will be prefixed by underscore
		; %2 = number to write
		; %3 = minimal number of digits, 0..5. defaults to 1
		;	(setting it to 0 with a number of 0 defines macro to "")
%macro _autodigitsstrdef 2-3.nolist 1
%if %3 > 5
 %error Minimal number of digits 6 or more: %3
%endif
%define _%1 ""
%if (%2) >= 100000
 %error Number has to use 6 or more digits: %2
%endif
%if (%2) >= 10000 || %3 >= 5
_appenddigitstrdef %1, %2 / 10000 % 10
%endif
%if (%2) >= 1000 || %3 >= 4
_appenddigitstrdef %1, %2 / 1000 % 10
%endif
%if (%2) >= 100 || %3 >= 3
_appenddigitstrdef %1, %2 / 100 % 10
%endif
%if (%2) >= 10 || %3 >= 2
_appenddigitstrdef %1, %2 / 10 % 10
%endif
%if (%2) >= 1 || %3 >= 1
_appenddigitstrdef %1, %2 / 1 % 10
%endif
%endmacro

		; %1 = name of single-line macro to set. will be prefixed by underscore
		; %2 = number to write
		; %3 = minimal number of hexits, 0..8. defaults to 1
		;	(setting it to 0 with a number of 0 defines macro to "")
%macro _autohexitsstrdef 2-3.nolist 1
%if %3 > 8
 %error Minimal number of hexits 9 or more: %3
%endif
%define _%1 ""
%if (%2) >= 1_0000_0000h
 %error Number has to use 9 or more hexits: %2
%endif
%if (%2) >= 1000_0000h || %3 >= 8
_appenddigitstrdef %1, (%2 >> (7 * 4)) & 0Fh
%endif
%if (%2) >= 100_0000h || %3 >= 7
_appenddigitstrdef %1, (%2 >> (6 * 4)) & 0Fh
%endif
%if (%2) >= 10_0000h || %3 >= 6
_appenddigitstrdef %1, (%2 >> (5 * 4)) & 0Fh
%endif
%if (%2) >= 1_0000h || %3 >= 5
_appenddigitstrdef %1, (%2 >> (4 * 4)) & 0Fh
%endif
%if (%2) >= 1000h || %3 >= 4
_appenddigitstrdef %1, (%2 >> (3 * 4)) & 0Fh
%endif
%if (%2) >= 100h || %3 >= 3
_appenddigitstrdef %1, (%2 >> (2 * 4)) & 0Fh
%endif
%if (%2) >= 10h || %3 >= 2
_appenddigitstrdef %1, (%2 >> (1 * 4)) & 0Fh
%endif
%if (%2) >= 1h || %3 >= 1
_appenddigitstrdef %1, (%2 >> (0 * 4)) & 0Fh
%endif
%endmacro


		; Compute required words/dwords/qwords/paragraphs/pages/KiB of known byte size (rounds up if necessary)
%idefine bytes(b)	(b)
%idefine words(b)	((b)+1>>1)
%idefine dwords(b)	((b)+3>>2)
%idefine qwords(b)	((b)+7>>3)
%idefine paragraphs(b)	((b)+15>>4)
%idefine paras(b)	((b)+15>>4)
%idefine pages(b)	((b)+511>>9)
%idefine kib(b)		((b)+1023>>10)

		; Compute required bytes of known word/dword/qword/paragraph/page/KiB size
%idefine frombytes(b)		(b)
%idefine fromwords(w)		((w)<<1)
%idefine fromdwords(d)		((d)<<2)
%idefine fromqwords(q)		((q)<<3)
%idefine fromparagraphs(p)	((p)<<4)
%idefine fromparas(p)		((p)<<4)
%idefine frompages(p)		((p)<<9)
%idefine fromkib(k)		((k)<<10)


		; Compute relative address
		;
		; address: which address is referenced
		; fixup: how many bytes are added to get the absolute address
%define __REL__(address,fixup) ((address)-(fixup))
%xdefine __REL8__(address,fixup) __REL__(address,fixup)
%xdefine __REL8__(address) __REL__(address,$+1)	; for __JMP_REL8 (db)
%xdefine __REL16__(address,fixup) __REL__(address,fixup)
%xdefine __REL16__(address) __REL__(address,$+2); for __JMP_REL16 (dw)

		; Values of some opcodes
%define __JMP_REL8 0EBh
%define __JMP_REL16 0E9h
%define __JMP_FAR 0EAh
%define __CALL_REL16 0E8h
%define __CALL_FAR 9Ah
%define __NOP 90h
%define __TEST_IMM8 0A8h			; changes flags, NC
%define __TEST_IMM16 0A9h			; changes flags, NC
					; Longer NOPs require two bytes, like a short jump does.
					; However they execute faster than unconditional jumps.
					; This one reads random data in the stack segment.
					;  (Search for better ones.)
%define __TEST_OFS16_IMM8 0F6h,86h		; changes flags, NC

		; Remove the following macros if already found in other files
%unimacro asciz 0-1+.nolist
%unimacro ascii 0-1+.nolist
%unmacro _ascii_prefix_suffix 2-3+.nolist
%undef ascii
%undef asciz
%undef ascic
%undef asciiline
%undef ascizline
%undef ascicline
%unimacro verdef 4.nolist
%unimacro numdef 1-3.nolist
%unimacro strdef 3.nolist
%unimacro incdef 2-4.nolist
%unimacro _incdef 2-4.nolist
%unimacro incdef 2-*.nolist
%unimacro excdef 2-*.nolist
%unimacro comment 0-*.nolist
%unimacro commentif 2.nolist
%unimacro commentifn 2.nolist
%unimacro fixme 0-1+.nolist
%unimacro noparam 0-1+.nolist
%unimacro noparamnamed 1-2+.nolist
%unimacro assignif 3-4.nolist
%unimacro assignifn 3-4.nolist
%unimacro cpu 1+.nolist				; removes NASM's standard macro
%unimacro selcpu 0.nolist
%unimacro cpufailmsg 0-2+.nolist
%unimacro check186 0-1.nolist
%unimacro check286 0-1.nolist
%unimacro check386 0-1.nolist
%unimacro check186 0-3.nolist
%unimacro check286 0-3.nolist
%unimacro check386 0-3.nolist
%unimacro checkcpu 1.nolist
%unimacro _d 2.nolist
%unimacro d0out 0-3.nolist
%unimacro endarea 1-2.nolist
%unimacro struc 1.nolist			; removes NASM's standard macro
%unimacro struc 1-2.nolist
%unimacro endstruc 0.nolist			; removes NASM's standard macro
%unimacro endstruc 0-1.nolist
%unimacro istruc 1.nolist			; removes NASM's standard macro
%unimacro at 1-2+.nolist			; removes NASM's standard macro
%unimacro iend 0.nolist				; removes NASM's standard macro
%unimacro fill 2-3+.nolist
%unimacro _fill 3.nolist
%unimacro pad 2-3+.nolist
%unimacro _pad 3.nolist
%unimacro alignd 2-3+.nolist



%if __NASM_VERSION_ID__ >= (2 * 100_0000h + 15 * 1_0000h)
 %pragma preproc sane_empty_expansion true
%endif

		; String prefixed and/or suffixed with sequence(s)
		;
		; %1 = prefix
		; %2 = suffix
		; %3+ = Optional string
	%macro _ascii_prefix_suffix 2-3+.nolist
		db %1
%ifnempty %3
		db %3
%endif
		db %2
	%endmacro

%idefine ascii _ascii_prefix_suffix "","",
%idefine asciz _ascii_prefix_suffix "",0,
%idefine ascic _ascii_prefix_suffix "",36,
%idefine asciiline _ascii_prefix_suffix "",{13,10},
%idefine ascizline _ascii_prefix_suffix "",{13,10,0},
%idefine ascicline _ascii_prefix_suffix "",{13,10,36},

		; Counted string
		;
		; %1 = data type of count
		; %2+ = optional string
	%imacro _counted 2+.nolist
%push
 %ifnempty %2
		_strlen %2
		%1 %$len
		db %2
 %else
		%1 0
 %endif
%pop
	%endmacro
	%idefine counted _counted db,
	%idefine countedb _counted db,
	%idefine countedw _counted dw,

	%imacro _strlen 1-*.nolist
%assign %$len 0
%rep %0
 %ifstr %1
  %strlen %$addlen %1
  %assign %$len %$len+%$addlen
 %elifnempty %1
  %assign %$len %$len+1
 %endif
 %rotate 1
%endrep
	%endmacro


		; Define version information.
		;
		; %1 = Base name of single-line macros
		; %2 = Additional letter for base name of single-line macros
		; %3 = Major version
		; %4 = Minor version
		;
		; Created single-line macros:
		; %1_VER%2 = version word, high byte = major version
		; %1_VER%2_X = version word (bytes exchanged), high byte = minor version
		; %1_VER%2_MAJ = Major version number byte
		; %1_VER%2_MIN = Minor version number byte
		; %1_VER%2_STR = Up to 7 character version string (maximal "255.255")
	%imacro verdef 4.nolist
%assign %1_VER%2_MAJ	%3
%assign %1_VER%2_MIN	%4
%assign %1_VER%2	ver(%3,%4)
%assign %1_VER%2_X	verx(%3,%4)
%push
 %defstr %$maj		%3
 %defstr %$min		%4
 %strlen %$length %$min
 %if %$length < 2
  %strcat %$min		'0', %$min		; one digit low numbers padded to two digits
 %endif
 %strcat %1_VER%2_STR	%$maj, '.', %$min
%pop
	%endmacro

		; Define a numeric definition for conditional assembly
		;
		; Instead of:
		; %ifdef DEFINE
		; use:
		; %if _DEFINE
		;
		; %1 = Definition name. Will get an additional underscore prefix
		; %2 = Value to assign if not defined (defaults to 0)
		; %3 = Value to assign if defined, but empty (defaults to 1)
	%imacro numdef 1-3.nolist 0,1
%ifnum __DEFAULTED_%1
 %if __lMACROS1_MAC__DEFAULTING && __DEFAULTED_%1
  %undef _%1			; cause defaulting to take effect again
  %if __DEFAULTED_%1 == 2	; defaulted to third parameter?
   %define _%1			; cause alternative defaulting to take effect again
  %endif
 %endif
%endif
%ifdef _%1
 %ifempty _%1
  %assign _%1 %3		; i.e. "-d_DEFINE" option on NASM's command line
  %if __lMACROS1_MAC__DEFAULTING
   %assign __DEFAULTED_%1 2	; note alternative defaulting occurred
  %endif
 %endif				;  (if option was "-d_DEFINE=0", it's left as zero)
 %if __lMACROS1_MAC__DEFAULTING
  %ifnnum __DEFAULTED_%1
   %assign __DEFAULTED_%1 0	; note no defaulting occurred
  %endif
 %endif
%else				; If not defined (no option on NASM's command line)
 %assign _%1 %2			;  then assign to zero
 %if __lMACROS1_MAC__DEFAULTING
  %assign __DEFAULTED_%1 1	; note defaulting occurred
 %endif
%endif
	%endmacro


		; Define a generic definition for conditional assembly
		;
		; %1 = Definition name. Will get an additional underscore prefix
		; %2 = What to define as if not defined (defaults to empty)
		; %3 = What to define as if defined, but empty (defaults to empty)
	%imacro gendef 1-3.nolist
%ifnum __DEFAULTED_%1
 %if __lMACROS1_MAC__DEFAULTING && __DEFAULTED_%1
  %undef _%1			; cause defaulting to take effect again
  %if __DEFAULTED_%1 == 2	; defaulted to third parameter?
   %define _%1			; cause alternative defaulting to take effect again
  %endif
 %endif
%endif
%ifdef _%1
 %ifempty _%1
  %define _%1 %3		; i.e. "-d_DEFINE" option on NASM's command line
  %if __lMACROS1_MAC__DEFAULTING
   %assign __DEFAULTED_%1 2	; note alternative defaulting occurred
  %endif
 %endif				;  (if option was "-d_DEFINE=0", it's left as zero)
 %if __lMACROS1_MAC__DEFAULTING
  %ifnnum __DEFAULTED_%1
   %assign __DEFAULTED_%1 0	; note no defaulting occurred
  %endif
 %endif
%else				; If not defined (no option on NASM's command line)
 %define _%1 %2			;  then define to default
 %if __lMACROS1_MAC__DEFAULTING
  %assign __DEFAULTED_%1 1	; note defaulting occurred
 %endif
%endif
	%endmacro


;;	%macro __help_strdef_stringify 3+.nolist
;;%xdefine _%1 %2%3%2
;;	%endmacro

		; Define a string definition
		;
		; %1 = Definition name. Will get an additional underscore prefix
		; %2 = Value to assign if not defined
		; %3 = Value to assign if defined, but no string (if not given, %2)
		;
		; Using braces { and } is useful for %2 and %3
	%imacro strdef 2-3.nolist
%ifnum __DEFAULTED_%1
 %if __lMACROS1_MAC__DEFAULTING && __DEFAULTED_%1
  %undef _%1			; cause defaulting to take effect again
  %if __DEFAULTED_%1 == 2	; defaulted to third parameter?
   %define _%1			; cause alternative defaulting to take effect again
  %endif
 %endif
%endif
%ifdef _%1
 %ifempty _%1
  %if %0 >= 3
   %define _%1 %3		; i.e. "-d_DEFINE" option on NASM's command line
   %if __lMACROS1_MAC__DEFAULTING
    %assign __DEFAULTED_%1 2	; note alternative defaulting occurred
   %endif
  %else
   %define _%1 %2
   %if __lMACROS1_MAC__DEFAULTING
    %assign __DEFAULTED_%1 1	; note defaulting occurred
   %endif
  %endif
 %else
  %ifnstr _%1
; refer to http://stackoverflow.com/questions/15650276/nasm-convert-integer-to-string-using-preprocessor
   %defstr _%1 _%1
;    %define %%quot "
;    %define %%stringify(x) %%quot %+ x %+ %%quot
;    %xdefine _%1 %%stringify(_%1)
;;    __help_strdef_stringify %1, ", _%1
	; issue: leading and trailing blanks are deleted out of the string.
	; workaround: quote the content so that it's a string already.
	; ie this may need nasm source.asm -D_"' content '"
  %endif
 %endif				;  (if option was "-d_DEFINE='FOO'", it's left as 'FOO')
%else				; If not defined (no option on NASM's command line)
 %define _%1 %2			;
 %if __lMACROS1_MAC__DEFAULTING
  %assign __DEFAULTED_%1 1	; note defaulting occurred
 %endif
%endif
	%endmacro

		; Include definitions if a condition is met
		;
		; %1 = condition
		; %2-* = included definition name, without underscore prefix
	%imacro incdef 2-*.nolist
 %if %1				; if true
 %rep (%0 - 1)
%rotate 1
%assign _%1 1			; set any included to true
 %endrep
 %endif
	%endmacro

		; Exclude definitions if a condition is met
		;
		; %1 = condition
		; %2-* = excluded definition name, without underscore prefix
	%imacro excdef 2-*.nolist
 %if %1				; if true
 %rep (%0 - 1)
%rotate 1
%assign _%1 0			; set any excluded to false
 %endrep
 %endif
	%endmacro

	%imacro overridedef 1-2.nolist 0
%push OVERRIDE
%ifdef _%1
 %assign %$defined 1
%else
 %assign %$defined 0
%endif
%xdefine %$prior _%1
%xdefine %$defaulted __DEFAULTED_%1
%define %$name %1
%assign _%1 %2
%assign __DEFAULTED_%1 0
	%endmacro

	%imacro resetdef 0-1.nolist
%ifnctx OVERRIDE
 %error Wrong context
%endif
%if %0
 %ifnidn %1, %$name
  %error Wrong usage of resetdef
 %endif
%endif
%xdefine _%[%$name] %$prior
%xdefine __DEFAULTED_%[%$name] %$defaulted
%ifn %$defined
 %undef _%[%$name]
%endif
%pop
	%endmacro


%assign __lMACROS1_MAC__DEFAULTING 0
	%imacro defaulting 0-1.nolist 1
%ifidni %1,toggle
 %assign __lMACROS1_MAC__DEFAULTING !__lMACROS1_MAC__DEFAULTING
%else
 %assign __lMACROS1_MAC__DEFAULTING !!(%1)
%endif
	%endmacro

	numdef _lMACROS1_MAC__CPU_DEFAULTS
%if __lMACROS1_MAC__CPU_DEFAULTS
	numdef 186		; 186 opcodes allowed
	numdef 386		; 386 opcodes allowed
	incdef _386,186		; excluding 186 opcodes on 386+ seems not useful
%endif

	numdef _lMACROS1_MAC__DEBUG_DEFAULTS
%if __lMACROS1_MAC__DEBUG_DEFAULTS
	numdef DEBUG		; all of the debugging levels
	incdef _DEBUG,DEBUG0,DEBUG1,DEBUG2,DEBUG3,DEBUG4
	numdef DEBUG0		; 1st level debug
	numdef DEBUG1		; 2nd level debug
	numdef DEBUG2		; 3rd level debug
	numdef DEBUG3		; 4th level debug
	numdef DEBUG4		; 5th level debug
			; Note:	All specific debug levels are independent.
			;	What each debug level does is program-specific.
%endif


		; Automatically select CPU depending on definitions
		;
		; Use "hardwired" cpu directives instead in source files that
		; don't include code for multiple CPU generations.
	%imacro selcpu 0.nolist
%if _386
		cpu 386
%elif _186
		cpu 186
%else
		cpu 8086
%endif
	%endmacro

		; Write a message stating what CPU type is required.
		;
		; %1 = Part of message before numerical CPU identifier (Defaults to "")
		; %2 = Part of message after numerical CPU identifier (Defaults to ASCIZ " CPU required")
		;
		; Using braces { and } is useful
		;
		; No data is generated if assembling for 8086
	%imacro cpufailmsg 0-2+.nolist "",{" CPU required",0}
%if _386
		db %1, "386", %2
%elif _186
		db %1, "186", %2
%endif
	%endmacro

		; Check if required CPU is available
		;
		; The code uses cx, ax and comparison flags.
		;
		; %1 = Code to execute if CPU requirement not meet
	%imacro checkcpu 1.nolist
%if _386
%%checkcpu:	check386
.fail:
		%1
.pass:
%elif _186
%%checkcpu:	check186
.fail:
		%1
.pass:
%endif
	%endmacro

		; Check for 186+ CPU
		;
		; %1 = true if .fail follows (jump to .pass),
		;      false if .pass follows (jump to .fail)
		;      Default true
		; %2 = label to jump to if 186+ CPU
		;      Default .pass
		; %3 = label to jump to if no 186+ CPU
		;      Default .fail
	%imacro check186 0-3 1 .pass .fail
		mov cx, 1_0010_0001b		; Shift count's bit 0 and 5 are set; shift data is 1
		shl ch, cl			; 186+ limits shift count to five bits (0-4): resulting
						; ch is 10b on 186+; 0 on 8086 (actual result overflows)
%if %1
		jnz %2				; 186+ -->
%else
		jz %3				; 8086 -->
%endif
	%endmacro

		; Check for 286+ CPU
		;
		; %1 = true if .fail follows (jump to .pass),
		;      false if .pass follows (jump to .fail)
		;      Default true
		; %2 = label to jump to if 286+ CPU
		;      Default .pass
		; %3 = label to jump to if no 286+ CPU
		;      Default .fail

	%imacro check286 0-3 1 .pass .fail
		push sp
		pop ax
		cmp ax, sp			; 286+ pushes original sp value
%if %1
		je %2				; 286+ -->
%else
		jne %3				; 8086 or 186 -->
%endif
	%endmacro

		; Check for 386+ CPU
		;
		; %1 = true if .fail follows (jump to .pass),
		;      false if .pass follows (jump to .fail)
		;      Default true
		; %2 = label to jump to if 386+ CPU
		;      Default .pass
		; %3 = label to jump to if no 386+ CPU
		;      Default .fail
	%imacro check386 0-3 1 .pass .fail
		check286 0, {%2}, {%3}		; branches to .fail if 8086 or 186 -->
[cpu 286]
		pushf
		push wo 0111_0000_0000_0000b
		popf				; set bits 12-14 of FLAGS ("push imm16" instruction 186+)
		pushf
		pop ax				; get what was in FLAGS
		popf				; restore original flags
		test ax, 0111_0000_0000_0000b	; 386+ leaves what we pushed; 286 clears bits 12-14 in
						; Real Mode (Bits 12-14 are used in Protected Mode only)
__CPU__
%if %1
		jnz %2				; 386+ -->
%else
		jz %3				; 286 -->
%endif
	%endmacro

		; Instruction if specified level debug definition true
		;
		; %1 = Level of requested debug definition
		; %2 = Instruction
	%imacro _d 2+.nolist
%if _DEBUG%1
		%2
%endif
	%endmacro

		; Instruction if xth level debug definition true
		;
		; %1+ = Instruction
%idefine d0 _d 0,
%idefine d1 _d 1,
%idefine d2 _d 2,
%idefine d3 _d 3,
%idefine d4 _d 4,

		; Breakpoint if xth level debug definition true
%idefine dbp _d, __DEBUG_BP__
%idefine d0bp d0 __DEBUG0_BP__
%idefine d1bp d1 __DEBUG1_BP__
%idefine d2bp d2 __DEBUG2_BP__
%idefine d3bp d3 __DEBUG3_BP__
%idefine d4bp d4 __DEBUG4_BP__
%idefine dd0bp _d,{d0bp}
%idefine dd1bp _d,{d1bp}
%idefine dd2bp _d,{d2bp}
%idefine dd3bp _d,{d3bp}
%idefine dd4bp _d,{d4bp}

		; Breakpoint instructions for all debug levels
		;
		; "call ...", "int1", "int3" are useful single-instruction breakpoints
%define __DEBUG_BP__ int3
%define __DEBUG0_BP__ int3
%define __DEBUG1_BP__ int3
%define __DEBUG2_BP__ int3
%define __DEBUG3_BP__ int3
%define __DEBUG4_BP__ int3

		; Print number if 1st level debug definition true
		;
		; %1 = Number to display (in range 0-255)
		; %2 = Override call for "call __DEBUG0_DISPALDEC__"
		; %3 = Override call for "call __DEBUG0_DISPAL__"
		;
		; The calls have to preserve all registers except ax.
	%macro d0out 0-3.nolist -,call __DEBUG0_DISPALDEC__,call __DEBUG0_DISPAL__
%if _DEBUG0
	push ax
%ifnidn %1,-
	mov al, %1
	%2
%endif
	mov al, '.'
	%3
	pop ax
%endif
	%endmacro

		; cpu macro replacement
		;
		; __CPU__ = like __SECT__ (that is, use [cpu ...] and switch back with __CPU__)
	%imacro cpu 1+.nolist
%define __CPU__ [cpu %1]
__CPU__
	%endmacro

		; Define size of area
		;
		; %1 = Starting label of area
		; %2 = if true, use already defined label %1_size (for
		; use with NASM's struc and endstruc); default is false
		;
		; Provided labels: %1_size (byte), %1_size_b (same),
		; %1_size_w (words), %1_size_d (dwords), %1_size_q
		; (qwords), %1_size_p (paragraphs), %1_size_pg (pages),
		; %1_size_ki (KiB).
	%imacro endarea 1-2.nolist 0
%ifn %2
%1_size equ $ - %1
%endif
%1_size_b equ %1_size
%1_size_w equ %1_size+1 >>1
%1_size_d equ %1_size+3 >>2
%1_size_q equ %1_size+7 >>3
%1_size_p equ %1_size+15 >>4
%1_size_pg equ %1_size+511 >>9
%1_size_ki equ %1_size+1023 >>10
	%endmacro

		; The following macros replace NASM's standard struc macros.
		; They add support for non-zero starting offsets of structures
		; (optional %2 of struc is starting offset) and creating endarea's
		; various size definitions with endstruc (if optional %1 of endstruc
		; is true).
%imacro struc 1-2.nolist 0
%push
%define %$strucname %1
[absolute %2]
%$strucname:
%endmacro
%imacro endstruc 0-1.nolist 0
%if %1
endarea %$strucname
%else
%{$strucname}_size equ ($-%$strucname)
%endif
%pop
__SECT__
%endmacro

%imacro istruc 1.nolist
%push
%define %$strucname %1
%$strucstart:
%endmacro
%imacro at 1-2+.nolist
	  times (%1-%$strucname)-($-%$strucstart) db 0
	  %2
%endmacro
%imacro iend 0.nolist
	  times %{$strucname}_size-($-%$strucstart) db 0
%pop
%endmacro


	%imacro checkeven 1.nolist
%ifnnum %1
 %error Number expected
%endif
%if %1 & 1
 %error Even value expected
%endif
	%endmacro

		; Fill data to specified byte size (specify data)
		;
		; %1 = Byte size to fill
		; %2 = Fill byte (eg 0, 90h, 32)
		; %3 = Actual instruction to place before filling (optional)
	%imacro fill 2-3+.nolist
%%data:
		%3			; Actual data if given, else expands to nothing
		_fill %1,%2,%%data
	%endmacro


		; Fill data to specified byte size (specify start label of data)
		;
		; %1 = Byte size to fill
		; %2 = Fill byte (eg 0, 90h, 32)
		; %3 = Start label of data
	%imacro _fill 3.nolist
%if %1 < 0				; hu?
 %error _fill: size is negative
%elif $-(%3) < 0			; should generate a phase error anyway. however
 %error _fill: start label must not be behind filling
%elif (%1)-$+(%3) < 0			; times would also throw an error then ("TIMES value x is negative")
 %error _fill: data too large, exceeds size	; but showing a more specific message won't hurt
%endif
		times (%1)-$+(%3) db %2	; Fill with fill byte to given size
	%endmacro


		; Pad data to specified byte boundary (specify data)
		;
		; Does _not_ pad relative to the section start but relative to the start of the data. Use
		; NASM's align (or alignd below) to pad (cough, cough... align) if you don't want that.
		;
		; %1 = Byte boundary
		; %2 = Pad byte (eg 0, 90h, 32)
		; %3 = Actual instruction to place before padding (optional, else macro creates nothing)
	%imacro pad 2-3+.nolist
%%data:
		%3			; Actual data if given, else expands to nothing
		_pad %1,%2,%%data
	%endmacro


		; Pad data to specified byte boundary (specify start label of data)
		;
		; Does _not_ pad relative to the section start but relative to the start of the data. Use
		; NASM's align (or alignd below) to pad (cough, cough... align) if don't you want that.
		;
		; %1 = Byte boundary
		; %2 = Pad byte (eg 0, 90h, 32)
		; %3 = Start label of data (if it equals $, no padding is created)
	%imacro _pad 3.nolist
%if %1 < 0				; hu?
 %error _pad: boundary is negative
%elifn %1
 %error _pad: boundary is zero
%elif $-%3 < 0				; should generate a phase error anyway. however
 %error _pad: start label must not be behind padding
%endif

%push
 %assign %$size $-%3			; size of data
 %assign %$count 0
	%rep %1				; must be at the boundary then
 %ifn (%$count + %$size) % %1		; if ( !((paddedcount + size) modulo boundary) )
	%exitrep			; then { we're done }
 %endif
 %assign %$count %$count+1		; else keep counting
	%endrep
 %if %$count >= %1			; if loop end caused by %endrep
  %error pad: an unknown error occured
 %endif
		times (%$count-1) db %2	; put this data for the assembler. but in _one_ line
%pop
	%endmacro


		; Similar to pad but using align:
		;
		; %1 = Byte boundary (relative from the section start)
		; %2 = Align byte (eg 0, 90h, 32)
		; %3 = Actual instruction to place before aligning (optional, else macro still aligns!)
	%imacro alignd 2-3+.nolist
		%3
%if %1 < 0				; hu?
 %error alignd: boundary is negative
%elifn %1
 %error alignd: boundary is zero
%else
		align %1,db %2
%endif
	%endmacro


		; Comment a line out
		;
		; Purpose:	In front of instructions that are only to be created if
		;		an expressions results in true, place a valid single-line
		;		macro name. Then use the commentif (or commentifn) macro
		;		below to create a single-line macro from an expression.
		;
		;		This will use the macro "comment" so the instructions won't
		;		assemble if the expression doesn't result in true. (Or
		;		if the expression doesn't result in false, in case of
		;		commentifn.) Else the single-line macro gets defined to
		;		nothing causing the instructions to assemble. This "comment"
		;		macro is required because you can't define a single-line
		;		macro to the value of semicolon (";") with the preprocessor
		;		for obvious reasons. (It will think a real comment starts
		;		with the semicolon ;)
	%imacro comment 0-*.nolist
	%endmacro


		; Specify a single-line macro for above comment macro purpose
		;
		; %1 = expression (gets placed after %if)
		; %2 = single-line macro (gets underscore prefix)
	%imacro commentif 2.nolist
%if %1
%define _%2 comment			; define it to invoke comment
%else
%define _%2				; define it to a zero string
%endif
	%endmacro


		; Like commentif, but expression gets placed after %ifn
	%imacro commentifn 2.nolist
%ifn %1
%define _%2 comment			; define it to invoke comment
%else
%define _%2				; define it to a zero string
%endif
	%endmacro


		; Aborts assembling if found anywhere within source
	%imacro fixme 0-1+.nolist
%error %1
	%endmacro


		; Comment a line out but make sure there are no parameters
	%imacro noparam 0-1+.nolist
%if %0
 %error Must not have parameters.
%endif
	%endmacro


		; Like noparam, except %1 is a message to display
	%imacro noparamnamed 1-2+.nolist
%ifnempty %2
 %error %1: Must not have parameters.
%endif
	%endmacro


		; Assign a single-line macro to an value if a condition matches, else zero
		;
		; %1 = expression (gets placed after %if)
		; %2 = single-line macro (gets underscore prefix)
		; %3 = value if condition matches
		; %4 = value if condition doesn't match (default: 0)
	%imacro assignif 3-4.nolist 0
%if %1
%assign _%2 %3
%else
%assign _%2 %4
%endif
	%endmacro


		; Like assignif, but expression gets placed after %ifn
	%imacro assignifn 3-4.nolist 0
%ifn %1
%assign _%2 %3
%else
%assign _%2 %4
%endif
	%endmacro

%endif
[list +]
