
%if 0

lDebug expression evaluator

Copyright (C) 1995-2003 Paul Vojta
Copyright (C) 2008-2025 E. C. Masloch

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.

%endif


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2

error_expr:	nearcall error

 %if _DEBUG4 || _DEBUG5
  %define _DEB_ASM_PREFIX expr_
  %include "deb.asm"
 %endif

%else
	usesection lDEBUG_CODE

error_expr: equ error

 %if _DEBUG4 || _DEBUG5
expr_d4message equ d4message
 %endif
%endif

%xdefine _EXPR_SECTION _CURRENT_SECTION


;--- get a valid offset for segment in BX

		; INP:	bx = segment
		;	al = first character, si -> next character
		;	ah = 1 if might be a segment or pointer
		;	ah = 0 if must be an offset
		; OUT:	CY if pointer type return, bx:dx = pointer
		;	DebugX on a 386:
		;	 edx = offset (even if 16-bit PM/86M segment)
		;	 ah = 1 if a 32-bit segment, 0 if a 16-bit segment
		;	DebugX otherwise:
		;	 dx = offset
		;	 ah = 0
		;	Debug:
		;	 dx = offset
		;	al, si refer to next part of command line
		; CHG:	Debug: ah, DebugX: -
		; REM:	The byte [bAddr32] is no longer changed by this
		;	 function. The caller has to use the status that
		;	 is returned in ah, if desired.
		; REM:	It is expected that INP:bx is a selector in PM,
		;	 however it may be a segment or arbitrary value
		;	 as well. In this case a 32-bit offset input may
		;	 be rejected or accepted by this function.
getofsforbx: section_of_function
	push cx			; preserve
	push bx			; preserve
	mov ch, ah		; ch = 0 if must be an offset
%if _PM
_386	xor edx, edx		; properly initialize high word
	nearcall test_high_limit
	jz .16			; 16-bit segment -->
[cpu 386]
	nearcall getdword
	 push bx
	 push dx
	 pop edx		; edx = 32-bit offset
	nearcall checkpointer
	mov ah, 1		; return 32-bit offset flag
	jmp .ret_pop_NC
__CPU__
%endif

.16:
	nearcall getexpression
	nearcall checkpointer
	nearcall getword.checksignificantbits
%if _PM
	mov ah, 0		; return 16-bit offset flag
%endif
.ret_pop_NC:
	pop bx
	pop cx			; restore
	clc			; NC: not a pointer type
	retn

		; Internal function for getofsforbx
		;
		; INP:	ah = return from getexpression
		;	 (flag 80h = pointer type expression)
		;	ch = 0 if must be an offset, 1 if may be pointer/segment
		;	bx:dx = return from getexpression
		;	ss:sp -> near return address (checkpointer),
		;		bx restore,
		;		cx restore,
		;		near return address (getofsforbx)
		; OUT:	returns near if not a pointer type expression
		;	returns to caller of getofsforbx if pointer type expr,
		;	 CY
		;	 bx:dx = expression
		;	 DebugX on a 386: edxh = 0
		;	 DebugX: ah = 0
		;	 cx = restored from stack
checkpointer: section_of_function
	test ah, ah		; pointer type ?
	jns .ret		; no -->
	test ch, ch		; can be a pointer or segment ?
	jz errorj10		; no -->
	pop cx			; discard a near return address
	pop cx			; discard bx
	pop cx			; restore cx
%if _PM
_386	movzx edx, dx		; edx = dx
	mov ah, 0		; always treat as 16-bit offset
%endif
	stc			; return a pointer type
.ret:
retn_expr:
	retn


;	GETRANGE - Get address range from input line.
;    a range consists of either start and end address
;    or a start address, a 'L' and a length.
;	Entry	AL	First character of range
;		SI	Address of next character
;		BX	Default segment to use
;		CX	Default length to use (or 0 if not allowed)
;		di	Default length in lines if nonzero
;			 (only used for getrange.lines entrypoint
;			  and with word getrange_lines & 8000h set)
;			 must be <= 7FFFh
;	Exit	AL	First character beyond range
;		SI	Address of the character after that
;		BX:(E)DX	First address in range
;		BX:(E)CX	Last address in range
;	Uses	AH

%if _PM
getrangeX: section_of_function
	and word [getrange_lines], 0
.lines: section_of_function getrangeX
 %if _MS_0RANGE_COMPAT
%if !_LOADER
	mov byte [getrange_is_uu], 0
%endif
.lines_and_uu: section_of_function getrangeX
 %endif
	clropt [internalflags3], dif3_accept_getrange_0
_386	movzx ecx, cx
.ecx_and_0_valid: section_of_function getrangeX
	_386_PM_o32
	push cx
	nearcall getaddrX
	jmp short getrange.common
%else
check_section_of getrangeX
check_section_of getrangeX.lines
 %if _MS_0RANGE_COMPAT
check_section_of getrangeX.lines_and_uu
 %endif
check_section_of getrangeX.ecx_and_0_valid
getrangeX: equ getrange
getrangeX.lines: equ getrange.lines
 %if _MS_0RANGE_COMPAT
getrangeX.lines_and_uu: equ getrange.lines_and_uu
 %endif
getrangeX.ecx_and_0_valid: equ getrange.ecx_and_0_valid
%endif

getrangeX_have_address_need_length: section_of_function
	and word [getrange_lines], 0
	clropt [internalflags3], dif3_accept_getrange_0
	_386_PM_o32
	xor cx, cx
	_386_PM_o32
	push cx			; save the default length
	jmp getrange.common


errorj10: jmp error_expr

getrange: section_of_function
	and word [getrange_lines], 0
.lines: section_of_function getrange
%if _MS_0RANGE_COMPAT
%if !_LOADER
	mov byte [getrange_is_uu], 0
%endif
.lines_and_uu: section_of_function getrange
%endif
	clropt [internalflags3], dif3_accept_getrange_0
_386_PM	movzx ecx, cx
.ecx_and_0_valid: section_of_function getrange
	_386_PM_o32
	push cx			; save the default length
	nearcall getaddr	; get address into bx:(e)dx (sets bAddr32) (returns edx)
.common:
	_386_PM_o32
	pop cx
	nearcall skipcomm0
	nearcall iseol?
	jne getrange_not_eol

	testopt [getrange_lines], 8000h
	jz @F			; if lines length not supported -->
	test di, di		; default lines given ?
	jz @F			; no -->
	js short errorj10	; error if sign bit set -->
	mov word [getrange_lines], di
				; return lines (PL)
	_386_PM_o32
	xor cx, cx		; (e)cx = 0
	inc cx			; placeholder length = 1
@@:

	mov ah, 2		; do not allow past boundary if F RANGE
	testopt [internalflags3], dif3_accept_getrange_0
				; F RANGE special case ?
	jnz @F			; yes -->
	_386_PM_a32
	jcxz errorj10		; if a range is mandatory
	xor ah, ah		; default range, allow clamping to boundary
@@:
getrange_length.addcheck:
_386_PM	cmp byte [bAddr32], 0
_386_PM	je .16
_386_PM	dec ecx
_386_PM	add ecx, edx
_386_PM	jnc getrange_retn	; if no wraparound
_386_PM	or ecx, byte -1		; go to end of segment
_386_PM	jmp short .checkclamped	; check if clamping allowed -->
.16:
_386_PM	test ah, 2		; F RANGE with implicit length ?
_386_PM	jz @F			; no -->
_386_PM	cmp ecx, 1_0000h	; yes, is it a 32-bit length > 64 KiB ?
_386_PM	ja short errorj10	; error if so (don't try to clamp) -->
@@:
_386_PM	cmp ecx, 0FFFFh		; was high ?
_386_PM	jbe @F			; no --> (ecxh = 0)
_386_PM	xor ecx, ecx		; ecxh = 0 and prepare so as to
				;  result in cx = FFFFh after dec
@@:
	dec cx
	add cx, dx
	jnc getrange_retn	; if no wraparound
	mov cx, -1		; go to end of segment
				; next, check if clamping allowed
.checkclamped:		; checks if clamping allowed
	test ah, ah		; clamping allowed ?
	jz getrange_retn
	jmp errorj10		; (no) if a specified length wrapped -->

getrange_not_eol:
	nearcall uppercase
	cmp al, 'L'
	je getrange_length	; if a range with length is given
	_386_PM_o32	; xchg ecx, edx
	xchg cx, dx		; ecx = start offset, edx clobbered
	mov dx, msg.end		; dx clobbered
	dec si			; -> nonblank
	nearcall isstring?	; (advance si if match)
	lodsb			; load separator byte or initial nonblank
	mov ah, 0		; must be offset
	nearcall getofsforbx_remember_bitness
				; (DebugX: returns edx no matter what)
	_386_PM_o32	; xchg ecx, edx
	xchg cx, dx		; ecx = end offset, edx = start offset
	_386_PM_o32	; cmp edx, ecx
	cmp dx, cx
	ja short errorj2b	; if empty range -->
getrange_retn:
	retn

getrange_length:
	dec si			; -> at 'L'
	push dx
	mov dx, msg.length
	nearcall isstring?	; if match: ZR, NC, else: NZ, CY
	pop dx
%if 0
	jne .notlength
	db __TEST_IMM8		; (skip inc, si -> terminator after "LENGTH")
.notlength:
	inc si			; -> behind 'L'
%else
	adc si, 0		; -> behind 'L' if mismatch,
				;  stay at -> behind "LENGTH" else
%endif
	nearcall skipcomma	; discard the 'L'
;--- a range is entered with the L/ength argument
;--- get a valid length for segment in BX
	push dx
	push bx
_386_PM	nearcall test_high_limit
_386_PM	pushf
	mov cx, word [getrange_lines]
				; cx from getrange_not_eol clobbered
	nearcall get_length.lines
_386_PM	popf
_386_PM	jnz .not16_64kib	; don't check for <= 64 KiB
	cmp bx, byte 1
	jb .not16_64kib		; < 64 KiB in 16-bit segment -->
	jne short errorj2b	; 16-bit segment, above 64 KiB -->
	test dx, dx
	jnz short errorj2b	; 16-bit segment, above 64 KiB -->
.not16_64kib:
@@:
%if _PM
_386	push bx			; (only push high word on 386+)
	push dx
	_386_o32	; pop ecx	; mov ecx, bxdx
	pop cx			; mov cx, dx
%else
	mov cx, dx
%endif
	or bx, dx		; zero ?
	jz short explicit0range	; yes, error -->
	pop bx
	pop dx
	mov ah, 1		; disallow clamping / overflow
	jmp .addcheck		; check for overflow

explicit0range:
%if _MS_0RANGE_COMPAT
	testopt [options2], opt2_0range_compat
	jz errorj2b
	inc bx			; bx:dx = 10000h (64 KiB)
%if !_LOADER
	rol byte [getrange_is_uu], 1
	jnc @B			; if not uu -->
	xchg bx, dx		; bx:dx = 1
%endif
	jmp @B
%endif
errorj2b:
	jmp	error_expr


;	GETADDR - Get address from input line.
;	Entry	AL	First character of address
;		SI	Address of next character
;		BX	Default segment to use
;	Exit	AL	First character beyond address
;		SI	Address of the character after that
;		BX:(E)DX	Address found
;	Uses	AH,CX
;	REM:	mm expects that numeric expressions evaluating
;		 to numbers in the range 0 to C3h are always
;		 allowed here regardless the default segment's
;		 D/B bit and limit. This is needed for its mc
;		 command detection.

getaddr: section_of_function
%if _PM
	nearcall getaddrX
%if _EXPRDUALCODE && _DUALCODE
	nearcall verifysegm_or_error	; make BX a writeable segment
	retn
%else
	jmp verifysegm_or_error	; make BX a writeable segment
%endif

; getaddrX differs from getaddr in that BX is not ensured
; to be writeable in PM.
;
; For DEBUG without PM support, getaddr is getaddrX. Both don't return CF.

getaddrX: section_of_function
	mov byte [bAddr32], 0
	xor cx, cx
	cmp al, '$'		; a real-mode segment?
	jne getaddr_no_dollar
	lodsb
%if _DOUBLEDOLLAR
	cmp al, '$'
	jne @F
	lodsb
	dec cx			; indicate double dollar sign (0FFFFh)
@@:
%endif

_386	xor edx, edx		; edxh = 0
	nearcall getexpression
	test ah, ah		; pointer type ?
	jns @F

	nearcall ispm		; need to translate ?
	jnz .ret		; no -->
				; bx = segment
		; after falling through we get bx:(e)dx = segmented address

check_section_of seg_bx_to_sel
seg_bx_to_sel: equ $		; (no base for local labels)
	push ax
	mov ax, 0002h
	int 31h
	jc short errorj2
	mov bx, ax		; bx = segment
%if _DOUBLEDOLLAR
	 push dx
	push cx
	xor cx, cx
	mov dx, -1
	mov ax, 0008h
	int 31h			; set segment limit 0FFFFh
	pop cx
	jcxz .no_double_dollar
	mov dx, cx		; cx = dx = 0FFFFh
	mov ax, 0008h
	int 31h			; set segment limit 0FFFF_FFFFh
.no_double_dollar:
	 pop dx
%endif
	pop ax
.ret:
	retn

@@:
	nearcall getword.checksignificantbits
	nearcall ispm		; need to translate ?
	jnz .checkseg		; no -->
	mov bx, dx
	nearcall seg_bx_to_sel
	mov dx, bx
.checkseg:
	nearcall skipwh0
	cmp al, ':'		; was a segment at all?
	je getaddr_segment	; yes -->
errorj2:
	jmp error_expr
%else
getaddrX: section_of_function
	cmp al, '$'
	jne getaddr_no_dollar
	lodsb
%if _DOUBLEDOLLAR
	cmp al, '$'
	jne @F
	lodsb
@@:
%endif
	nearcall getexpression
	test ah, ah		; pointer type ?
	js .ret			; return bx:dx = segmented address
	nearcall getword.checksignificantbits
	nearcall skipwh0
	cmp al, ':'
	je getaddr_segment
	jmp error_expr

.ret:
	retn
%endif


%if !_LOADER
	usesection lDEBUG_DATA_ENTRY
	align 2, db 0
getaddr_taken_table:
	dw msg.t, 1
	dw msg.taken, 1
	dw msg.nt, 0
	dw msg.nottaken, 0
	dw 0			; end of table
%endif

%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif
getaddr_no_dollar:
	dec si			; si -> text
%if !_LOADER
	push di
	mov di, getaddr_taken_table - 2
				; -> table minus 2
@@:
	scasw			; di += 2, -> -> string to compare
	mov dx, [di]		; -> string to compare (0 if end of table)
	scasw			; di += 2, -> taken status (1 = taken)
	test dx, dx		; end of table ?
	jz @F			; if end of table -->
	nearcall isstring?	; is it this keyword ?
	jne @B			; no, loop -->
	mov ax, [di]		; get taken status
	jmp getaddr_taken	; branch to taken handling, di on stack

@@:
	pop di			; restore di
%endif
	lodsb			; reload al = text, si -> after
	mov ah, 1		; might be a pointer type
	nearcall getofsforbx
	jc .ret			; if pointer type then return -->
	nearcall skipwh0
	cmp al, ':'
	je getaddr_segment_check_high
				; if this is a segment/selector -->
.ret:
%if _PM
	jmp short remember_bitness
				; remember 32-bitness (only if no segment)
%else
	retn
%endif

getaddr_segment_check_high:
_386_PM cmp edx, 0001_0000h	; segment/selector fits in word ?
_386_PM jae short errorj2	; no -->
getaddr_segment:
		; al = colon ':'
		; si -> text after colon
	mov bx, dx		; mov segment into BX
	nearcall skipwhite	; skip to next word
	mov ah, 0		; must be an offset
%if _PM
getofsforbx_remember_bitness: section_of_function
	nearcall getofsforbx
remember_bitness: section_of_function
	or byte [bAddr32], ah	; remember 32-bitness
	retn
%else
	jmp getofsforbx

check_section_of getofsforbx_remember_bitness
getofsforbx_remember_bitness: equ getofsforbx
%endif


%if !_LOADER
		; INP:	ax = 1 if taken, 0 if nottaken
		;	ss:sp -> di value
getaddr_taken: section_of_function
	_386_PM_o32	; (push esi)
	push si
	_386_PM_o32	; (push eax)
	push ax			; ! must be the LAST word in this stack frame

	mov dx, 15		; DL = number of bytes to go, DH = prefix flags
	mov bx, word [reg_cs]
	_386_PM_o32	; mov esi, dword [reg_eip]
	mov si, word [reg_eip]
.pp2:
	nearcall pp16		; get next instruction byte into AL
	mov di, ppbytes
	mov cx, PPLEN_ONLY_PREFIXES
	repne scasb		; asize, osize, seg prefixes (ie, hints) ?
	jne @F			; no -->
	mov al,byte [di+PPLEN-1]; get corresponding byte in ppinfo
	; test al, PP_PREFIX	; prefix ?
	; jz pp3		; no -->
		; (Always set in getaddr_taken.)
	or dh, al		; set the OSIZE or ASIZE flags if either of these
			; Note:	Multiple OSIZE in a 16-bit cs do not toggle
			;	between decoding as O32 and O16, they're always
			;	decoded as O32. The same is true for A32, and
			;	in a 32-bit cs for O16 and A16.
.next:
	dec dl
	jnz .pp2		; if not out of bytes -->
.pp5:				; unknown, error out on the (NOT)TAKEN keyword
	_386_PM_o32	; (pop eax)
	pop ax
	_386_PM_o32	; (pop esi)
	pop si			; restore si so error will be behind keyword
	pop di
	inc si
	jmp errorj2a

@@:
	cmp al, 70h		; jcc rel8 ?
	jb @F			; no -->
	cmp al, 7Fh
	jbe .rel_8		; yes -->
@@:
	cmp al, 0E0h		; loop* / j(e)cxz rel8 ?
	jb @F			; no -->
	cmp al, 0E3h
	jbe .rel_8		; yes -->
@@:
	cmp al, 0Fh		; prefix byte ?
	jne .pp5		; no, no valid conditional branch (error) -->
	nearcall pp16
	cmp al, 80h		; jcc rel16/rel32 ?
	jb .pp5
	cmp al, 8Fh
	ja .pp5			; no, error -->
.rel_16_32:
_386_PM	nearcall pp_fix32bitflags
	nearcall pp16
	xchg al, ah
	nearcall pp16
	xchg al, ah		; ax = rel16
_386_PM	test dh, PP_OPSIZ
_386_PM	jz @F			; have 16-bit displacement -->
_386_PM	rol eax, 16		; preserve low 16 bits in high 16 bits
_386_PM	nearcall pp16
_386_PM	ror eax, 8
_386_PM	nearcall pp16
_386_PM	ror eax, 8		; magic swap to put high 16 bits where they belong
	jmp @FF			; have 32-bit displacement -->
		; (When _PM=0 build or no 386, this branch will do the
		;  same thing as taking it as a 16-bit displacement.)

.rel_8:
	nearcall pp16		; get 8-bit displacement
	cbw			; 8 to 16 bits
@@:
_386_PM	cwde			; 16 to 32 bits
@@:
	pop dx			; get ax from stack
	push dx			; restore it to the stack
	test dl, dl		; 1 if taken, 0 if nottaken
	jz @F			; nottaken, esi has eip value after inst -->
	_386_PM_o32	; (add esi, eax)
	add si, ax		; taken: apply displacement to esi
@@:
	_386_PM_o32
	mov dx, si		; get into (e)dx, bx already has seg/sel
_386_PM	nearcall test_d_b_bit
_386_PM	jnz @F			; if 32-bit cs -->
_386_PM	movzx edx, dx		; clear high 16 bits
@@:
	_386_PM_o32	; (pop eax)
	pop ax
	_386_PM_o32	; (pop esi)
	pop si
	pop di
_386_PM	mov ah, 0		; do not remember bitness (is 16 bit)
_386_PM	jz @F			; if 16-bit cs -->
_386_PM	inc ah			; remember bitness as 1 (is 32 bit)
@@:
_386_PM	nearcall remember_bitness
%if _EXPRDUALCODE && _DUALCODE
	nearcall skipwhite
	retn
%else
	jmp skipwhite
%endif
%endif

%if _STRNUM
getstr_check_limit:
	cmp di, word [getstr_dispatch_table.limit + bx]
	jbe getstr_retn
%endif
errorj2a:
	jmp error_expr


;	GETSTR - Get string of bytes.  Put the answer in line_out.
;		Entry	AL	first character
;			SI	address of next character
;		Exit	[line_out] first byte of string
;			DI	address of last+1 byte of string
;		Uses	AX, cx, dx,SI

getstr: section_of_function
%if _STRNUM
	push bx
	xor bx, bx		; 0 = byte size as default
%endif
	mov di, line_out
	nearcall iseol?
	je short errorj2a	; we don't allow empty byte strings
getstr_loop:
%if _STRNUM
	nearcall skipcomm0
	dec si			; -> nonblank
	mov dx, msg.as		; "AS" keyword ?
	nearcall isstring?
	lodsb
	jne .notas		; no -->
	nearcall skipcomm0
	dec si			; -> nonblank
	nearcall get_length_keyword
	jnz errorj2a
	cmp cl, 2		; 0 = bytes, 1 = words, 2 = dwords
				; (ch is always zero)
	ja errorj2a		; higher is invalid -->
	mov bx, cx
	add bx, bx		; make a table offset
	lodsb
	jmp getstr_next

.notas:
%endif
	cmp al, "'"
	je getstr_start_quote	; if string
	cmp al, '"'
	je getstr_start_quote	; ditto

%if _STRNUM
	call getstr_check_limit
	call near [getstr_dispatch_table.numeric + bx]
%else
	nearcall getbyte	; byte in DL
	mov byte [di], dl	; store the byte
	inc di
%endif
	jmp short getstr_next

getstr_start_quote:
	mov ah, al		; save quote character
getstr_loop_quoted:
	lodsb
	cmp al, ah
	je getstr_quote_quoted	; if possible end of string
	nearcall iseol?.notsemicolon
	je short errorj2a	; if end of line
getstr_store_quoted:
%if _STRNUM
	call getstr_check_limit
	call near [getstr_dispatch_table.string + bx]
%else
	stosb			; save character and continue
%endif
	jmp short getstr_loop_quoted

getstr_quote_quoted:
	lodsb
	cmp al, ah
	je getstr_store_quoted	; if doubled quote character
getstr_next:
	nearcall skipcomm0	; go back for more
	nearcall iseol?
	jne getstr_loop			; if not done yet
%if _STRNUM
	pop bx
getstr_retn:
%endif
	retn


%if _STRNUM
getstr_numeric_byte:
	nearcall getbyte	; byte in DL
	mov byte [di], dl	; store the byte
	inc di
	retn

getstr_numeric_word:
	nearcall getword	; dx
	mov word [di], dx	; store
	scasw			; di += 2
	retn

getstr_numeric_dword:
	push bx
	nearcall getdword	; bx:dx
	mov word [di], dx	; store
	scasw			; di += 2
	mov word [di], bx	; store
	scasw			; di += 2
	pop bx
	retn


getstr_string_word:
	stosb			; save character and continue
	mov al, 0
	stosb
	retn

getstr_string_dword:
	stosb			; save character and continue
	mov al, 0
	stosb
	stosb
getstr_string_byte:
	stosb
	retn


	usesection lDEBUG_DATA_ENTRY

		; REM:	Dispatch table in section lDEBUG_CODE or lDEBUG_CODE2
	align 2, db 0
getstr_dispatch_table:
.numeric:	dw getstr_numeric_byte
		dw getstr_numeric_word
		dw getstr_numeric_dword
.string:	dw getstr_string_byte
		dw getstr_string_word
		dw getstr_string_dword
.limit:		dw line_out_end - 1
		dw line_out_end - 2
		dw line_out_end - 4

%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif

%endif	; _STRNUM


		; INP:	al =, si -> input
		;	cx = getrange lines flag (8000h if LINES allowed)
		; OUT:	al =, si -> after number / keyword
		;	If not LINES,
		;	 word [getrange_lines] unchanged
		;	 bx:dx = size
		;	If LINES,
		;	 word [getrange_lines] = number of lines
		;		(must be >= 1 and <= 7FFFh)
		;	 bx:dx = 1, as a placeholder value
		; CHG:	ah, cx
		; REM:	jumps to error on invalid input
get_length: section_of_function
	xor cx, cx		; do not allow LINES
.lines: section_of_function get_length

	nearcall getdword
	dec si
	push si			; -> before comma
	nearcall skipcomma

	 push dx		; = low word of number
	dec si

	test cx, cx		; LINES allowed ?
	jns @F			; no -->

	mov dx, msg.lines
	nearcall isstring?	; LINES specified ?
	jne @F			; no -->

	 pop dx			; restore = low word of number
	test bx, bx		; lines must be <= 7FFFh
	jnz .error		; if > FFFFh -->
	test dx, dx
	jz .error		; lines mustn't be zero -->
	js .error		; if >= 8000h -->
	mov word [getrange_lines], dx
				; save lines specified
	mov dx, 1		; return a placeholder size in bx:dx
	jmp .done

.error:
	jmp error_expr

@@:
	nearcall get_length_keyword
				; si -> terminator after length unit
				;  (or -> after expression if no unit)
	 pop dx			; restore = low word of number
	jnz .nokeyword		; no unit given, reset to -> before comma -->
	jcxz .noshift		; "BYTES" given -->
.shiftloop:
	shl dx, 1
	rcl bx, 1
	jc short .error		; too large -->
	loop .shiftloop		; loop shifting (if it was shift count > 1)
.noshift:
.done:
	pop cx			; discard -> before comma
	nearcall skipwhite	; al = next character, si -> after that
	retn

.nokeyword:
	pop si			; restore -> before comma
	lodsb			; al = comma or other nonblank, si -> after
	retn


		; INP:	si -> potential keyword
		; OUT:	NZ if no keyword,
		;	 si unchanged
		;	 cx = 0 (default length shift like BYTES)
		;	ZR if keyword found,
		;	 si -> separator behind keyword
		;	 cx = length shift (< 256)
		; CHG:	dx, al
get_length_keyword: section_of_function
	push di
	mov di, table_length_keywords
	db __TEST_IMM16		; (skip jcxz in first iteration)
@@:
	jcxz .gotsize		; prior was the last entry --> (NZ)
		; This branch cannot be taken in the first iteration.
		;  If it is taken then the flags are always NZ here.
	mov cx, word [di]	; = shift count (0 if last entry of table)
	scasw			; di += 2
	mov dx, word [di]	; -> keyword in allcaps ASCIZ
	scasw			; di += 2
	nearcall isstring?	; this keyword matches ?
	jne @B			; no, loop --> (NZ, si unchanged)
		; matched, si is advanced -> past the keyword
		; ZR

.gotsize:
	pop di
	retn


isbracketorunaryoperator?: section_of_function
	nearcall isunaryoperator?
	je .yes
%if _INDIRECTION
	cmp al, '['
	je .yes
%endif
	cmp al, '('
.yes:
	retn


		; Is al one of the simple unary operators?
		; OUT:	NZ if not
		;	ZR if so,
		;	 NC
		;	 cx = index into unaryoperators
isunaryoperator?: section_of_function
	push di
	mov di, unaryoperators	; -> countedw
	jmp short isoperator?.common

		; See previous description.
isoperator?: section_of_function
	push di
	mov di, operators	; -> countedw
.common:
	mov cx, word [di]	; get amount of scan byte entries
	push cx
	scasw			; skip amount
	repne scasb		; scan, cx = remainder of entries
	pop di			; = amount
	jne .no			; --> (NZ)
		; neg cx = not cx \ inc cx
		; therefore not cx = neg cx \ dec cx
	not cx			; minus remainder minus 1
	add cx, di		; amount minus remainder minus 1
		; if last matches, remainder = 0
		; if second to last matches, remainder = 1
		; therefore amount - remainder - 1 = 0-based index
	cmp al, al		; NC, ZR
.no:
	pop di
	retn


		; INP:	al = character
		; OUT:	al = capitalised character
		;	ZR, NC if a separator
		;	NZ if no separator
isseparator?: section_of_function
.:
	nearcall uppercase
	push cx
%if _EXPRESSIONS
	cmp al, 'A'
	jb @F
	cmp al, 'Z'
	jbe @FF
@@:
	nearcall isoperator?	; normal operators are separators (also handles equality sign)
	je .yes			; if match --> (ZR, NC)
@@:
%endif
	push di
	mov di, separators
	mov cx, word [di]	; = amount of scan byte entries
	scasw			; skip amount
	repne scasb		; ZR, NC on match, NZ else
	pop di
.yes:
	pop cx
	retn

		; INP:	al = character
		; OUT:	al = capitalised character
		;	ZR, NC if a separator (not L or dot)
		;	NZ if no separator (including L or dot)
.except_L_or_dot: section_of_function isseparator?
	nearcall uppercase
	cmp al, '.'
	je .ret_NZ
	cmp al, 'L'
	jne .
.ret_NZ:
	test al, al
	retn


		; Does one of the type operators start in input?
		;
		; INP:	al = first character
		;	si-> next character
		; OUT:	Iff NC,
		;	 bx>>1 = offset into typebitmasks and typehandlers tables
		;	 bx&1 = set iff signed type
		;	 di-> behind the type operator
		; CHG:	bx, cx, di
		;
		; Note:	Signed types are specified by an S prefix to
		;	 the type names. Only non-address types can
		;	 be signed (that is, offset, segment, and
		;	 pointer cannot be signed).
		;	Types can be specified with abbreviated names,
		;	 except where that would clash with numeric
		;	 input or a register name or ambiguity would
		;	 be caused. These cases are:
		;	SS, S (short, seg, signed, ss)
		;	B (byte, numeric 0Bh)
		;	C (char, numeric 0Ch)
		;	D (dword, numeric 0Dh)
		;	3 (3byte, numeric 3)
		;	3B (3byte, numeric 3Bh)
		;	CH (char, register ch)
istype?: section_of_function
%if maxtypesize & 1
	mov cx, maxtypesize+1		; = maximum count + 1
%else
	mov cx, maxtypesize		; = maximum count
%endif
				; cx is even here!
	push dx
	push ax
	push si

	sub sp, cx			; allocate name buffer
	mov di, sp			; -> name buffer
	 push di
	xor bx, bx			; initialise count
%ifn maxtypesize & 1
	inc cx				; = maximum count + 1
%endif
				; The +1 does not represent an off-by-one
				;  because the below loop stores to the
				;  buffer at the beginning of subsequent
				;  iterations, after checking cx.

	db __TEST_IMM16			; (skip stosb and lodsb)
.storename:
	stosb				; store in name buffer
	lodsb				; get next character to check
	nearcall uppercase
	push cx
	nearcall isbracketorunaryoperator?; terminator ?
	pop cx
	je .endname
	nearcall iseol?
	je .endname
	cmp al, 32
	je .endname
	cmp al, 9
	je .endname
	cmp al, ','
	je .endname			; yes -->
				; We don't check for digits here.
				;  Immediate values and variables
				;  must leave a space inbetween.
	inc bx				; count characters
	loop .storename			; count remaining buffer space
				; Here, the potential name was too
				;  long for a valid type name.
	stc
	jmp short .done			; -->

.endname:
	nearcall skipwh0		; skip to next field
	dec si				; -> behind potential name
	 pop di				; -> name buffer
	mov cx, bx			; cx = length
	 push si			; save position in input line
	mov si, di			; si-> name buffer
	push bx
	push di
	clc				; indicate unsigned check
	nearcall isunsignedtype?	; matches an unsigned type ?
	pop si
	pop cx
	jnc .done			; yes -->

	lodsb				; al = first, si-> second character
	dec cx				; cx = length less one
	cmp al, 'S'			; first character an "S" ?
	stc				; (indicate signed check, or: no type)
	jne .done			; no, not signed either -->
	nearcall isunsignedtype?	; matches an unsigned type now ?
	inc bx				; if NC, set to indicate signed type
.done:
	lahf
	 pop di				; if NC, -> behind matched type name
	add sp, (maxtypesize+1) & ~1	; discard name buffer
	pop si
	sahf
	pop ax
	pop dx
	retn

		; Does one of the unsigned type operators start in buffer?
		;
		; INP:	si-> name buffer with capitalised potential name
		;	cx = length of potential name
		;	CY iff looking for signed type
		; OUT:	Iff NC,
		;	 bx>>1 = offset into typebitmasks and typehandlers tables
		;	 bx&1 = 0
		; CHG:	ax, bx, cx, dx, si, di
isunsignedtype?: section_of_function
	mov di, types
	 sbb dx, dx			; 0FFFFh if signed check else 0
	jcxz .notype			; if zero characters -->
	cmp cx, 2
	jne @F
	cmp word [si], "CH"
	je .notype
	cmp word [si], "3B"
	je .notype
@@:
	loop .single_character_checked	; if not single character -->

	lodsb				; get that character
	cmp al, 'S'			; specified "S" or "SS" ?
	je .notype			; yes, not allowed -->
	or al, dl			; iff signed check, al |= 0FFh
	dec si				; (restore)
	cmp al, '3'
	je .notype
	cmp al, 'A'			; specified only a valid digit ?
	jb .single_character_checked
	cmp al, 'F'+1
	jb .notype			; yes, not allowed -->

.single_character_checked:
	inc cx				; (restore)
	 and dx, types.addresses-types.end	; = 0 iff unsigned check
	xor ax, ax			; initialise ah, and ax = 0 first
	xor bx, bx
	xchg di, si
	 add dx, types.end		; = .addresses for signed check,
	 				;  = .end for unsigned check

		; Before each iteration,
		;  si-> byte-counted next name to check
		;  di-> potential name (in name buffer)
		;  cx = cl = length of potential name
		;  (dx-1) = maximum value for si
		;  ah = 0
		; Before the first iteration additionally,
		;  bx&~3 = index to return for this name (if match)
		;  al = 0
		; Before subsequent iterations additionally,
		;  (bx+2)&~3 = index to return for this name (if match)
		;  al = offset to add to si first
	db __TEST_IMM16			; (skip two times inc bx)
.loop:
	inc bx
	inc bx				; increase index
	add si, ax			; -> next table entry
	lodsb				; ax = length of full name
	cmp si, dx			; checked all allowed names?
	jae .notype			; yes, done -->
	cmp ax, cx			; full name large enough ?
	jb .loop			; no -->
	push di
	push cx
	push si
	 repe cmpsb			; potential name matches ?
	pop si
	pop cx
	pop di
	jne .loop			; no -->

	and bl, ~3			; conflate alternative type names
	db __TEST_IMM8			; (NC, skip stc)
.notype:
	stc
	retn


	usesection lDEBUG_DATA_ENTRY

		; Table of bit masks and shift counts to determine
		;  how a type modifies the bit mask of required bytes.
		;
		; It would be possible to always retrieve a full dword
		;  from memory to process indirection in expressions,
		;  but this could fault if accessing inexistent data.
		;  Hence the debugger should minimise memory access.
		; For this reason, types allow the expression evaluator
		;  to keep track which of the term's bytes are actually
		;  going to be used. The bit mask of required bytes
		;  indicates which bytes are not discarded by any of a
		;  term's type operators.
		;
		; The second byte of each entry (applied to ch by the
		;  reader, ie high byte of cx) indicates a mask to
		;  apply to the bit mask of required bytes. Note that
		;  this mask is applied first, before the shift that's
		;  described next.
		; The first byte of each entry (loaded into cl by the
		;  reader, ie low byte of cx) indicates a shift left
		;  count to apply to the bit mask of required bytes.
		;  (Only the segment type doesn't have 0 currently.)
		;
		; Note that types are parsed forwards through the input
		;  (ie the specified command) but are actually applied
		;  to the numeric value they refer to backwards, that
		;  is, a type that is closer to the term in the input
		;  is applied to the term's result before a type that's
		;  farther from the term.
		; Misleadingly, this reversal isn't very apparent in
		;  most processing of the type and unary operators.
		; The segment type's shifting and masking reflects the
		;  reversal: while the actual operation is to shift
		;  right then restrict to the low word, the entry in
		;  this table indicates to restrict the bit mask to
		;  the low word then shift left.
	align 2, db 0
typebitmasks:
	db 0,    1b	; byte
	db 0,   11b	; word
	db 0,  111b	; 3byte
	db 0, 1111b	; dword
	db 0, 1111b	; pointer
	db 0,   11b	; offset
	db 2,   11b	; segment


		; Dispatch table for type conversion functions.
		;
		; INP:	bx:dx = dword input
		;	CF = signedness of type conversion
		;	ah from lahf with the same CF as current
		;	al = type (80h = pointer, 40h = signed)
		; OUT:	bx:dx = new value
		;	ah = type (80h = pointer, 40h = signed)
		; CHG:	ax
		; REM:	Dispatch table in section lDEBUG_CODE or lDEBUG_CODE2
	align 2, db 0
typehandlers:
	dw handlebyte
	dw handleword
	dw handle3byte
	dw handledword
	dw handlepointer
	dw handleoffset
	dw handlesegment


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif

handlesegment: section_of_function
	mov dx, bx
	xor ah, ah		; NC, ah = 0
	jmp short handleword
handleoffset equ handleword

handle3byte: section_of_function
	mov bh, 0
	jnc .zero		; (iff unsigned type -->)
	test bl, bl		; signed ?
	jns .zero_f		; no -->
	dec bh
.zero_f:
	sahf			; restore CF
.zero:
	jmp handledword

handlebyte: section_of_function
	mov dh, 0
	jnc .zero		; (iff unsigned type -->)
	test dl, dl		; signed ?
	jns .zero_f		; no -->
	dec dh
.zero_f:
	sahf			; restore CF
.zero:
handleword: section_of_function
	mov bx, 0
	jnc .zero		; (iff unsigned type -->)
	test dx, dx		; signed ?
	jns .zero_f		; no -->
	dec bx
.zero_f:
	sahf			; restore CF
.zero:
handledword: section_of_function
	jc .signed		; if signed -->
	xor ah, ah		; return pointer=0 signed=0
	retn

.signed:
	mov ah, 40h		; return pointer=0 signed=1
	retn

handlepointer: section_of_function
	mov ah, 80h		; return pointer=1 signed=0
	retn


	usesection lDEBUG_DATA_ENTRY

		; List of binary and unary operators.
		; The order has to match that in the respective
		; operator handler dispatch table below.
	align 2, db 0
operators:
.:		countedw "oOaAxXcC?","+-*/%<>=!|&^"
.amount: equ $ - . - 2
	align 2, db 0
unaryoperators:
.:		countedw "+-~!?"
.amount: equ $ - . - 2


		; Dispatch table for unary operators,
		;  used by getexpression.
		; Functions in this table are called with:
		;
		; INP:	bx:dx = number
		;	ah = type
		; OUT:	bx:dx = result
		;	ah = type
		; CHG:	-
		; REM:	Dispatch table in section lDEBUG_CODE or lDEBUG_CODE2
	align 2, db 0
unaryoperatorhandlers:
.:
	dw uoh_plus		; +
	dw uoh_minus		; -
	dw uoh_not_bitwise	; ~
	dw uoh_not_boolean	; !
	dw uoh_abs		; ?
.amount: equ ($ - .) / 2
%if .amount != unaryoperators.amount
 %error String and table mismatch
%endif


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif

uoh_abs: section_of_function
	and ah, ~ 40h		; make type signed=0
	test bh, 80h		; negative ?
	jnz calculate_minus_bxdx; yes -->
	retn			; no, done

uoh_minus: section_of_function
	or ah, 40h		; make type signed=1
calculate_minus_bxdx: section_of_function
	neg bx
	neg dx
	sbb bx, byte 0		; neg bx:dx
uoh_plus: section_of_function	; (nop)
	retn

uoh_not_bitwise: section_of_function
	mov ah, 0		; make type pointer=0 signed=0
	not bx
	not dx
	retn

uoh_not_boolean: section_of_function
	mov ah, 0		; make type pointer=0 signed=0
	nearcall toboolean
	xor dl, 1		; toggle only bit 0
	retn


	usesection lDEBUG_DATA_ENTRY

		; Word table operatordispatchers: order as in string operators
		; Pointed functions dispatch depending on operator characters
		; Return: operator index, 0 = invalid

		; Word table operator_default_indexes: order as in string operators
		; Default INP:bx for operator dispatchers functions.
		; This is used to optimise the operator dispatchers.

		; Word table operator_default_strings: order as in string operators
		; Default INP:dx for operator dispatchers functions.
		; This is used to optimise the operator dispatchers.

		; Operator index (byte):
		; 0 = invalid, no operator found
		; 1.. = 1-based index in byte table operatorprecedences
		;     = 1-based index in word table operatorfunctions
		;     = 1-based index in word table operatornames

	struc opprecs
OPPREC_INVALID:	resb 1
OPPREC_COND:	resb 1
OPPREC_BOOL_OR:	resb 1
OPPREC_BOOL_XOR:resb 1
OPPREC_BOOL_AND:resb 1
OPPREC_COMPARE:	resb 1
OPPREC_BIT_OR:	resb 1
OPPREC_BIT_XOR:	resb 1
OPPREC_BIT_AND:	resb 1
OPPREC_BIT_CLR:	resb 1
OPPREC_SHIFT:	resb 1
OPPREC_ADDSUB:	resb 1
OPPREC_MULDIV:	resb 1
OPPREC_POWER:	resb 1
OPPREC_RIGHTOP:		; (to process it first in getexpression)
	endstruc
		; The number of precedence levels indicates how many
		; intermediate results getexpression might have to save
		; on its stack. With thirteen levels of precedence, up to
		; twelve intermediate results are pushed by getexpression.
		; (With 6 bytes each, that gives a moderate 72 bytes.)
		; Key to this is that, in case of a low enough operator
		; behind the one that triggered the pushing, the pushed
		; value will be popped before proceeding. This way more
		; intermediate results may be pushed later but the stack
		; never holds intermediate results that don't need to be
		; on the stack.

		; This is the definition of operator index values. The tables
		; operatorprecedences and operatorfunctions are ordered by this.
		; The operator dispatchers return one of these.
	struc ops
OPERATOR_INVALID:		resb 1	; 0 - invalid
OPERATOR_PLUS:			resb 1	; +
OPERATOR_MINUS:			resb 1	; -
OPERATOR_MULTIPLY:		resb 1	; *
OPERATOR_DIVIDE:		resb 1	; /
OPERATOR_MODULO:		resb 1	; %
OPERATOR_POWER:			resb 1	; **
OPERATOR_CMP_BELOW:		resb 1	; <
OPERATOR_CMP_BELOW_EQUAL:	resb 1	; <=
OPERATOR_CMP_ABOVE:		resb 1	; >
OPERATOR_CMP_ABOVE_EQUAL:	resb 1	; >=
OPERATOR_CMP_EQUAL:		resb 1	; ==
OPERATOR_CMP_NOT_EQUAL:		resb 1	; !=
OPERATOR_SHIFT_LEFT:		resb 1	; <<
OPERATOR_SHIFT_RIGHT:		resb 1	; >>
OPERATOR_SHIFT_RIGHT_SIGNED:	resb 1	; >>>
OPERATOR_BIT_MIRROR:		resb 1	; ><
OPERATOR_BIT_CLR:		resb 1	; clr (bitwise AND with bitwise NOT)
OPERATOR_BIT_OR:		resb 1	; |
OPERATOR_BIT_XOR:		resb 1	; ^
OPERATOR_BIT_AND:		resb 1	; &
OPERATOR_BOOL_OR:		resb 1	; ||
OPERATOR_BOOL_XOR:		resb 1	; ^^
OPERATOR_BOOL_AND:		resb 1	; &&
OPERATOR_COND:			resb 1	; ?? :: construct (special)
OPERATOR_RIGHTOP:		resb 1	; (dummy right-operand operator)
	endstruc
		; Order of BIT_* needs to be the same as that of BOOL_*.
		; BOOL_* have to follow directly behind BIT_*.
		; "R CF op= expr" depends on that (rr4 in rr.asm).


		; Dispatch table for (binary) expression operators,
		;  used by getexpression.
		; Functions in this table are called with:
		; INP:	al = operator character (which is also implicit)
		;	si-> remaining line (directly) behind operator character
		;	bx = value from operator_default_indexes
		;	dx = value from operator_default_strings,
		;	 random value if unused (the table is truncated)
		; OUT:	bl != 0 if a valid operator,
		;	 bl = operator index
		;	 si-> behind the last character belonging to the operator
		;	bl = 0 if no valid operator
		; CHG:	al, bh, dx
		; REM:	Dispatch table in section lDEBUG_CODE or lDEBUG_CODE2
	align 2, db 0
operatordispatchers:
.:
	dw od_o			; o
	dw od_o			; O
	dw od_a			; a
	dw od_a			; A
	dw od_x			; x
	dw od_x			; X
	dw od_c			; c
	dw od_c			; C
	dw od_cond		; ?

	dw od_plus		; +
	dw od_minus		; -
	dw od_multiply		; * **
	dw od_divide		; /
	dw od_modulo		; %
	dw od_below		; < <> <= <<
	dw od_above		; > >< >= >> >>>
	dw od_equal		; == =< =>
	dw od_not		; !=
	dw od_or		; | ||
	dw od_and		; & &&
	dw od_xor		; ^ ^^
.end: equ $ - .
.amount: equ ($ - .) / 2
%if .amount != operators.amount
 %error String and table mismatch
%endif
%if .amount > 100h
 %error Too many operator dispatchers
%endif


	align 2, db 0
operator_default_indexes:
.:
	dw OPERATOR_BIT_OR	; o
	dw OPERATOR_BIT_OR	; O
	dw OPERATOR_BIT_AND	; a
	dw OPERATOR_BIT_AND	; A
	dw OPERATOR_BIT_XOR	; x
	dw OPERATOR_BIT_XOR	; X
	dw OPERATOR_BIT_CLR	; c
	dw OPERATOR_BIT_CLR	; C
	dw OPERATOR_COND	; ?

	dw OPERATOR_PLUS	; +
	dw OPERATOR_MINUS	; -
	dw OPERATOR_MULTIPLY	; * **
	dw OPERATOR_DIVIDE	; /
	dw OPERATOR_MODULO	; %
	dw OPERATOR_CMP_NOT_EQUAL ; < <> <= <<
	dw OPERATOR_CMP_ABOVE	; > >< >= >> >>>
	dw OPERATOR_CMP_EQUAL	; == =< =>
	dw OPERATOR_CMP_NOT_EQUAL ; !=
	dw OPERATOR_BIT_OR	; | ||
	dw OPERATOR_BIT_AND	; & &&
	dw OPERATOR_BIT_XOR	; ^ ^^
.end: equ $ - .
.amount: equ ($ - .) / 2
%if .amount != operators.amount
 %error String and table mismatch
%endif
%if .amount > 100h
 %error Too many operator dispatchers
%endif


	align 2, db 0
operator_default_strings:
.:
	dw msg.r	; o
	dw msg.r	; O
	dw msg.nd	; a
	dw msg.nd	; A
	dw msg.or	; x
	dw msg.or	; X
	dw msg.lr	; c
	dw msg.lr	; C
	dw msg.questionmark	; ?
.end: equ $ - .
.amount: equ ($ - .) / 2
%if .amount > operators.amount
 %error String and table mismatch
%endif
%if .amount > 100h
 %error Too many operator dispatchers
%endif


operatorprecedences:
.:
	db OPPREC_INVALID		; need this for some checks
	db OPPREC_ADDSUB		; +
	db OPPREC_ADDSUB		; -
	db OPPREC_MULDIV		; *
	db OPPREC_MULDIV		; /
	db OPPREC_MULDIV		; %
	db OPPREC_POWER			; **
	db OPPREC_COMPARE		; <
	db OPPREC_COMPARE		; <=
	db OPPREC_COMPARE		; >
	db OPPREC_COMPARE		; >=
	db OPPREC_COMPARE		; ==
	db OPPREC_COMPARE		; !=
	db OPPREC_SHIFT			; <<
	db OPPREC_SHIFT			; >>
	db OPPREC_SHIFT			; >>>
	db OPPREC_SHIFT			; ><
	db OPPREC_BIT_CLR		; clr
	db OPPREC_BIT_OR		; |
	db OPPREC_BIT_XOR		; ^
	db OPPREC_BIT_AND		; &
	db OPPREC_BOOL_OR		; ||
	db OPPREC_BOOL_XOR		; ^^
	db OPPREC_BOOL_AND		; &&
	db OPPREC_COND			; ?? :: construct
	db OPPREC_RIGHTOP		; getexpression: no number yet
.amount: equ $ - .
%if .amount != ops_size
 %error Operators table size mismatch
%endif


		; REM:	Dispatch table in section lDEBUG_CODE or lDEBUG_CODE2
	align 2, db 0
operatorfunctions:
.:
	dw error_expr			; should not be called
	dw of_plus			; +
	dw of_minus			; -
	dw of_multiply			; *
	dw of_divide			; /
	dw of_modulo			; %
	dw of_power			; **
	dw of_compare_below		; <
	dw of_compare_below_equal	; <=
	dw of_compare_above		; >
	dw of_compare_above_equal	; >=
	dw of_compare_equal		; ==
	dw of_compare_not_equal		; !=
	dw of_shift_left		; <<
	dw of_shift_right		; >>
	dw of_shift_right_signed	; >>>
	dw of_bit_mirror		; ><
	dw of_clr_bitwise		; clr
	dw of_or_bitwise		; |
	dw of_xor_bitwise		; ^
	dw of_and_bitwise		; &
	dw of_or_boolean		; ||
	dw of_xor_boolean		; ^^
	dw of_and_boolean		; &&
	dw of_cond			; ?? :: construct
	dw of_rightop			; set to right operand
.amount: equ ($ - .) / 2
%if .amount != ops_size
 %error Operators table size mismatch
%endif


%if _EXPRESSION_DEBUG

%define OPNAMES db ""
%macro opname 1.nolist
%defstr %%string %1
%substr %%prefix %%string 1,3
%ifidni %%prefix, "of_"
 %substr %%string %%string 4,-1
%endif
%xdefine OPNAMES OPNAMES,%%name:,{asciz %%string}
	dw %%name
%endmacro

%macro opnamesstrings 1-*
 %rep %0
%1
  %rotate 1
 %endrep
%endmacro

	align 2, db 0
operatornames:
.:
	opname invalid			; invalid
	opname of_plus			; +
	opname of_minus			; -
	opname of_multiply		; *
	opname of_divide		; /
	opname of_modulo		; %
	opname of_power			; **
	opname of_compare_below		; <
	opname of_compare_below_equal	; <=
	opname of_compare_above		; >
	opname of_compare_above_equal	; >=
	opname of_compare_equal		; ==
	opname of_compare_not_equal		; !=
	opname of_shift_left		; <<
	opname of_shift_right		; >>
	opname of_shift_right_signed	; >>>
	opname of_bit_mirror		; ><
	opname of_clr_bitwise		; clr
	opname of_or_bitwise		; |
	opname of_xor_bitwise		; ^
	opname of_and_bitwise		; &
	opname of_or_boolean		; ||
	opname of_xor_boolean		; ^^
	opname of_and_boolean		; &&
	opname of_cond			; ?? :: construct
	opname right			; set to right operand
.amount: equ ($ - .) / 2
%if .amount != ops_size
 %error Operators table size mismatch
%endif

	opnamesstrings OPNAMES
%endif

%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif


od_multiply: section_of_function
	cmp byte [si], al
	jne .ret
	inc si
	mov bl, OPERATOR_POWER
.ret:
	retn

od_above: section_of_function
	cmp byte [si], al
	je .shr
	cmp byte [si], '<'
	je .mirror
	cmp byte [si], '='
	jne od_retn
.cmp_ae:
	inc si
	mov bl, OPERATOR_CMP_ABOVE_EQUAL
	retn
.shr:
	inc si
	cmp byte [si], al
	mov bl, OPERATOR_SHIFT_RIGHT_SIGNED
	je .inc_si
%if OPERATOR_SHIFT_RIGHT_SIGNED - 1 != OPERATOR_SHIFT_RIGHT
 %error Unexpected layout
%endif
	dec bx			; = OPERATOR_SHIFT_RIGHT
	retn
.mirror:
	mov bl, OPERATOR_BIT_MIRROR
.inc_si:
	inc si
	retn

od_below: section_of_function
	cmp byte [si], al
	je .shl
	cmp byte [si], '>'
	je .ncmp		; (default index)
	mov bl, OPERATOR_CMP_BELOW
	cmp byte [si], '='
	jne short od_retn

.cmp_be:			; bl = OPERATOR_CMP_BELOW_EQUAL
%if OPERATOR_CMP_BELOW + 1 != OPERATOR_CMP_BELOW_EQUAL
 %error Unexpected layout
%endif
.shl:				; bl = OPERATOR_SHIFT_LEFT
%if OPERATOR_CMP_NOT_EQUAL + 1 != OPERATOR_SHIFT_LEFT
 %error Unexpected layout
%endif
	inc bx
.ncmp:
od_not.ncmp:
od_equal.inc_si:
	inc si
	retn

od_equal: section_of_function
	cmp byte [si], '>'
	je od_above.cmp_ae
	cmp byte [si], '<'
	je od_below.cmp_be
	cmp byte [si], al
	je .inc_si
				; no valid operator
		; fall through, next cmp is also a mismatch

od_not: section_of_function
	cmp byte [si], '='
	je .ncmp		; (default index)
od_equal.invalid:
od_string_common.invalid:
	mov bl, OPERATOR_INVALID
	retn

od_or: section_of_function
od_and: section_of_function
od_xor: section_of_function
	cmp byte [si], al
	jne od_retn
	inc si
%if OPERATOR_BOOL_XOR - OPERATOR_BIT_XOR != OPERATOR_BOOL_AND - OPERATOR_BIT_AND
 %error Unexpected layout
%endif
%if OPERATOR_BOOL_XOR - OPERATOR_BIT_XOR != OPERATOR_BOOL_OR - OPERATOR_BIT_OR
 %error Unexpected layout
%endif
	add bl, OPERATOR_BOOL_XOR - OPERATOR_BIT_XOR

od_minus: section_of_function
od_plus: section_of_function
od_divide: section_of_function
od_modulo: section_of_function
od_retn: section_of_function
	retn

od_cond: section_of_function
od_o: section_of_function
od_a: section_of_function
od_x: section_of_function
od_c: section_of_function
od_string_common: section_of_function
	nearcall isstring?
	jne .invalid
	retn


		; (Binary) Expression operator functions,
		;  used by getexpression.
		; These functions are called with:
		; INP:	dword [hhvar] = previous number (left-hand operand)
		;	byte [hhtype] = previous type
		;	bx:dx = following number (right-hand operand)
		;	ah = following type
		; OUT:	bx:dx = result
		;	ah = type
		; CHG:	ax, cx
		; Note:	Errors (divisor zero) are currently simply handled
		;	 by jumping to "error".
		;	getexpression mustn't be called after until hhvar is
		;	 no longer used, as the call might overwrite hhvar.
		;
		; Type info (in ah and b[hhtype]) appears to be correctly
		; passed to here already. However, how should that be used?
		;
		; Quite simply, doing any arithmetic on two unsigned numbers
		; could return the smallest possible unsigned type (so that
		; if the result is <= 255 then the type is 1, if <= 65536
		; then the type is 2, else the type is 4).
		;
		; Handling two signed numbers here might be equally simple:
		; if the result is >= -128 && <= 127 then the type is 81h,
		; if the result is >= -32768 && <= 32767 then the type is
		; 82h, otherwise the type is 84h. Have to look into this.
		; (How does this interact with the unsignedness of the
		; actual computations?)
		;
		; It gets hairy when one operand is signed and the other
		; isn't; generally, two sub-cases of this exist: first, the
		; signed operand is positive, second, the signed operand is
		; negative. (Whether this distinction actually makes sense
		; for the implementation is still to be determined.)
		; Possible models:
		; * Result is always signed.
		; * Result is always unsigned(?!).
		; * Result is treated as signed, but if it's positive its
		;   type is changed to unsigned.
		;
		; It is also possible that operators could be handled
		; differently, for example, (some) bit and boolean
		; operators could imply unsigned operands in any case.
		; (>>> obviously implies a signed left operand already.)
		;
		; Note that (signed) negative bit shifting counts could
		; imply reversing the operation; << becomes >> and such.
		;
		; Note that for the addition of, for example, bit rotation,
		; it would be useful to retain the originally used type
		; inside getdword. At the end of getdword, the current bit
		; counting could be changed to use the "signed" bit of the
		; types and then determine which signed or unsigned type is
		; large enough to hold the value. (It might already work
		; mostly like that.) (Is this specifically about binary
		; operators? - No. In fact, binary operators are the most
		; likely to be (one of) the syntactic element(s) which
		; should change (and possible 'optimize') types. - Then
		; this might still apply to unary operators, and brackets
		; and parentheses. In particular, the later should call
		; a different entry or instruct getdword not to optimize
		; the type at the end so as to retain it. - Although in
		; cases where that matters, the parentheses are arguably
		; unnecessary, aren't they?)
		;
		; It has to be decided whether there should be implicit
		; dispatching based on the operands' types' signedness.
		; For example, currently (with all operands being implied
		; to be unsigned) there exist >> and >>>, and there could
		; exist > and S>. With implicit signedness dispatching, the
		; behaviour of >> would change: it would expose the current
		; >>'s behaviour with an unsigned (left) operand, and the
		; current >>>'s behaviour with a signed (left) operand.
		; (Either U(nsigned)>> and S(igned)>> operators could then
		; exist, which would imply an unsigned or signed left
		; operand respectively, or the affected operands' signedness
		; could be changed with the currently available prefix or
		; possible new postfix unary operators.
of_modulo: section_of_function
	push word [hhvar+2]
	push word [hhvar]
	push bx
	push dx
	call of_divide.no_hh_div; bx:dx := prev / foll
	pop word [hhvar]
	pop word [hhvar+2]
	nearcall of_multiply	; bx:dx := (prev / foll) * foll
	pop word [hhvar]
	pop word [hhvar+2]
;	jmp short of_minus	; bx:dx := prev - ((prev / foll) * foll)

of_minus: section_of_function
	nearcall calculate_minus_bxdx
of_plus: section_of_function
	add dx, word [hhvar]
	adc bx, word [hhvar+2]
or_hhtype: section_of_function
	or ah, byte [hhtype]
	retn

of_multiply: section_of_function; bx:dx := var * bx:dx
	or byte [hhtype], ah
	push di			; di is used as temporary storage
	mov ax, dx		; low word
	push ax			; save low word
	mul word [hhvar]	; low * low
	xchg di, ax		; di = low word result, clobber ax
	pop ax
	 push bx
	mov bx, dx		; bx = high word result 1
	mul word [hhvar+2]	; low * high
	add bx, ax		; bx = high word result 1 plus low * high
	 pop ax			; high word
	mul word [hhvar]
	add bx, ax		; bx = high word result
	; bx*[hhvar+2] not required, completely overflows
	mov dx, di		; dx = low word result
	pop di			; restore those
set_hhtype: section_of_function
	mov ah, byte [hhtype]
	retn

of_divide: section_of_function	; bx:dx := var / bx:dx
%if _HHDIVREMAIN
	cmp word [hh_depth], 1
	jne @F
	mov byte [hh_div_active], -1
	mov word [hh_div_divisor], dx
	mov word [hh_div_divisor + 2], bx
	push word [hhvar + 2]
	push word [hhvar]
	pop word [hh_div_dividend]
	pop word [hh_div_dividend + 2]
@@:
%endif
.no_hh_div:
	or byte [hhtype], ah
	 push bx
	or bx, dx		; divisor zero ?
	 pop bx
	jz short .error		; divisor zero !
	_386_jmps .32		; 386, use 32-bit code -->
%ifn _ONLY386
	test bx, bx		; need only 16-bit divisor ?
	jnz .difficultdiv16	; nope -->
	mov cx, dx
	xor dx, dx
	mov ax, word [hhvar+2]	; dx:ax = high word of previous number
	div cx
	xchg bx, ax		; bx = ax, clobber ax
	mov ax, word [hhvar]	; ax = low word of previous number, dx = remainder
	div cx
	xchg dx, ax		; bx:dx = result, clobber ax
	jmp set_hhtype

.difficultdiv16:		; code adapted from Art of Assembly chapter 9
				; refer to http://www.plantation-productions.com/Webster/www.artofasm.com/DOS/ch09/CH09-4.html#HEADING4-99
	mov cx, 32
	push bp
	push si
	push di
	mov ax, word [hhvar]
	mov bp, word [hhvar+2]	; bp:ax = previous number
	xor di, di
	xor si, si		; clear variable si:di
	xchg ax, dx
	xchg bp, bx		; bx:dx = previous number, bp:ax = divisor
.bitloop:
	shl dx, 1
	rcl bx, 1
	rcl di, 1
	rcl si, 1		; si:di:bx:dx << 1
	cmp si, bp		; does the divisor fit into si:di here ?
	jne @F
	cmp di, ax
@@:
	jb .trynext		; no -->
.goesinto:
	sub di, ax
	sbb si, bp		; subtract divisor
	inc dx			; set a bit of the result (bit was zero before, never carries)
.trynext:
	loop .bitloop		; loop for 32 bits
	pop di
	pop si
	pop bp
	jmp set_hhtype
%endif

.error:
	jmp error_expr

%ifn _ONLYNON386
.32:
[cpu 386]
	push eax
	push ebx
	push edx		; to preserve the high words
	 push bx
	 push dx
	 pop ebx		; ebx = following number
	xor edx, edx
	mov eax, dword [hhvar]	; edx:eax = previous number
	div ebx
	pop edx
	pop ebx			; restore high words
	 push eax
	 pop dx
	 pop bx			; bx:dx = result
	pop eax			; restore high word of eax
__CPU__
	jmp set_hhtype
%endif


of_power: section_of_function
	or byte [hhtype], ah
	mov cx, bx
	mov ax, dx		; get exponent
	or bx, dx		; exponent zero ?
	mov bx, 0
	mov dx, 1
	jz .ret			; yes, return with result as 1 -->
	cmp word [hhvar], dx	; optimization:
	jne .notone
	cmp word [hhvar+2], bx
	je .ret			; if base is one (and exponent not zero), result is 1 -->
.notone:
	push bp

.loop:				; cx:ax non-zero here
	shr cx, 1
	rcr ax, 1		; exponent /= 2
	push ax
	push cx
	jnc .even		; if exponent was even -->
	nearcall of_multiply	; var *= base
.even:
		; In the last iteration, cx:ax might be zero here
		; making the next call unnecessary. Oh well.
	push bx
	push dx
	mov bx, word [hhvar+2]
	mov dx, word [hhvar]	; base
	nearcall of_multiply	;  * base = base squared
	mov word [hhvar+2], bx
	mov word [hhvar], dx	; store as new base
	pop dx
	pop bx
	pop cx
	pop ax

	mov bp, bx		; optimization:
	or bp, dx		;  register now zero ?
	jz .ret_bp		; if so, return now --> (multiplying zero always results in zero)
	mov bp, cx
	or bp, ax		; exponent now zero ?
	jnz .loop		; no, loop -->

.ret_bp:
	pop bp
.ret:
	jmp set_hhtype

of_compare_below_equal: section_of_function
	nearcall of_helper_compare
	jbe of_helper_compare_true
	retn

of_compare_below: section_of_function
	nearcall of_helper_compare
	jb of_helper_compare_true
	retn

of_compare_not_equal: section_of_function
	nearcall of_helper_compare
	jne of_helper_compare_true
	retn

of_compare_equal: section_of_function
	nearcall of_helper_compare
	je of_helper_compare_true
	retn

of_compare_above_equal: section_of_function
	nearcall of_helper_compare
	jae of_helper_compare_true
	retn

of_compare_above: section_of_function
	nearcall of_helper_compare
	ja of_helper_compare_true
	retn


		; Called by operator functions to convert a number to boolean
		;
		; INP:	bx:dx
		; OUT:	dx = 0 or 1
		;	bx = 0
		;	ZF
toboolean: section_of_function
	or bx, dx		; = 0 iff it was 0000_0000h
	cmp bx, byte 1		; CY iff it was 0000_0000h, else NC
	sbb dx, dx		; -1 iff it was 0000_0000h, else 0
	xor bx, bx

		; INP:	dx
		; OUT:	dx += 1
of_helper_compare_true: section_of_function
	inc dx			; bx:dx = 0 iff it was 0000_0000h, else 1

		; Dummy operator computation function,
		;  used when setting a register without operator (rr)
		;  and to initialize the first getexpression loop iteration
		; INP:	dword [hhvar] = previous number (left-hand operand)
		;	byte [hhtype] = previous type
		;	bx:dx = following number (right-hand operand)
		;	ah = following type
		; OUT:	bx:dx = result (right-hand operand)
		;	ah = type
		; CHG:	ax, cx
of_rightop: section_of_function
	retn


		; Called by operator functions to compare operands
		;
		; INP:	dword [hhvar], byte [hhtype]
		;	bx:dx, ah
		; OUT:	Flags as for "cmp d[hhvar], bxdx"
		;	bx:dx = 0
		;	ah = 0
of_helper_compare: section_of_function
	xor ah, ah		; type signed=0 pointer=0
	cmp word [hhvar+2], bx
	jne .ret
	cmp word [hhvar], dx
.ret:
	mov bx, 0
	mov dx, bx		; set both to zero (without affecting flags)
	retn

of_shift_right: section_of_function
	xor ah, ah
	nearcall of_helper_getshiftdata
.loop:
	shr bx, 1
	rcr dx, 1
	loop .loop
	retn

of_shift_right_signed: section_of_function
	mov ah, 40h
	nearcall of_helper_getshiftdata
.loop:
	sar bx, 1
	rcr dx, 1
	loop .loop
	retn

of_shift_left: section_of_function
	xor ah, ah
	nearcall of_helper_getshiftdata
.loop:
	shl dx, 1
	rcl bx, 1
	loop .loop
	retn

		; Called by operator functions to get shift data
		;
		; This returns to the next caller with the unchanged input
		; operand if the shift count is zero. Otherwise, large shift
		; counts are normalized so the returned value in cx is not
		; zero and not higher than 32. This normalization is not just
		; an optimization, it's required for shift counts that don't
		; fit into a 16-bit counter.
		;
		; INP:	bx:dx = shift count
		; OUT:	bx:dx = input operand
		;	If shift count is >= 1 and <= 32,
		;	 cx = shift count
		;	If shift count is > 32,
		;	 cx = 32
		;	If shift count is zero,
		;	 discards one near return address before returning
		; CHG:	cx
of_helper_getshiftdata: section_of_function
	mov cx, dx
	test bx, bx
	jnz .largeshift
	cmp dx, byte 32
	jb .normalshift
.largeshift:
	mov cx, 32		; fix at maximal shift count
.normalshift:
	mov dx, word [hhvar]
	mov bx, word [hhvar+2]
	jcxz .break		; shift count zero, return input -->
	retn

.break:
	pop cx			; discard near return address
	retn

of_bit_mirror: section_of_function
	xor ah, ah
	mov cx, dx
	test bx, bx
	jnz .large
	cmp dx, byte 64
	jb .normal
.large:
	xor bx, bx		; mirror count 64 or higher:
	xor dx, dx		;  all 32 bits mirrored with (nonexistent) zero bits
	retn
.normal:
	mov dx, word [hhvar]
	mov bx, word [hhvar+2]
	cmp cl, 1
	jbe .ret		; mirror count one or zero, return input -->
	push si
	push di

	push cx
	mov di, -1
	mov si, di
.loopmask:
	shl di, 1
	rcl si, 1
	loop .loopmask		; create mask of bits not involved in mirroring
	and si, bx
	and di, dx		; get the uninvolved bits
	pop cx

	push si
	push di			; save them
	xor si, si
	xor di, di		; initialize mirrored register
.loop:
	shr bx, 1
	rcr dx, 1		; shift out of original register's current LSB
	rcl di, 1
	rcl si, 1		;  into other register's current LSB
	loop .loop
	pop dx
	pop bx			; restore uninvolved bits
	or bx, si
	or dx, di		; combine with mirrored bits

	pop di
	pop si
.ret:
	retn

of_or_bitwise: section_of_function
	or dx, word [hhvar]
	or bx, word [hhvar+2]	; bitwise or
or_hhtype_1: section_of_function
	jmp or_hhtype

of_or_boolean: section_of_function
	nearcall of_helper_getbool
	or dx, bx		; boolean or
	jmp short of_helper_retbool

of_clr_bitwise: section_of_function
	not bx
	not dx

of_and_bitwise: section_of_function
	and dx, word [hhvar]
	and bx, word [hhvar+2]	; bitwise and
	jmp or_hhtype_1

of_and_boolean: section_of_function
	nearcall of_helper_getbool
	and dx, bx		; boolean and
	jmp short of_helper_retbool

of_xor_bitwise: section_of_function
	xor dx, word [hhvar]
	xor bx, word [hhvar+2]	; bitwise xor
	jmp or_hhtype_1

of_xor_boolean: section_of_function
	nearcall of_helper_getbool
	xor dx, bx		; boolean xor
of_helper_retbool: section_of_function
	xor bx, bx		; high word always zero
	retn

		; Called by operator functions to convert operands to boolean
		;
		; INP:	bx:dx = next number, ah = type
		;	[hhvar] = previous number, [hhtype] = type
		; OUT:	bx = next number's boolean value
		;	dx = previous number's boolean value
		;	ah = 0
of_helper_getbool: section_of_function
	xor ah, ah
	nearcall toboolean
	push dx
	mov dx, word [hhvar]
	mov bx, word [hhvar+2]
	nearcall toboolean
	pop bx
	retn


	usesection lDEBUG_DATA_ENTRY

isvariable_data:
reverselfsrtop:	db 1Fh

%if _DEBUG3
	align 8, db 0
kregs:	dd 0
	dd 1
	dd 0aa55aa55h
	dd -1
	times 12 dd 0
%endif

	align 4, db 0
lfsr:		dd 2
reverselfsr:	dd 1
lfsrtap:	dd 8020_0003h
%if _SLEEP_NEW
getc_repeat_idle:	dw 0
sleep_repeat_idle:	dw 0
sleep_highest_delta:	dw 0
sleep_delta_limit:	dw 5
%endif
%if _COUNT || _SCOUNT
count_var:	dd 0
%endif
%if _RH
rhcount:	dw 0
%endif


		; REM:	Dispatch table in section lDEBUG_CODE or lDEBUG_CODE2
	align 2, db 0
isvariable_struc_list:

%assign IVS_ONEBYTE 1
isvariable_struc_onebyte_list:
	; name, size, flags, address, array last index, array skip
isvariablestruc "V", 4, 0, vregs, vregs.amount - 1
%if _DEBUG3
isvariablestruc "K", 4, 0, kregs, 15
%endif
isvariable_struc_onebyte_list_end:

%assign IVS_ONEBYTE 0
isvariable_struc_morebyte_list:

%if _EXTENSIONS && _EXT_VARIABLES
ext_var:
ext_var_amount equ _EXT_VARIABLES
ext_var_format equ 1
ext_var_size equ ISVARIABLESTRUC_size
 %rep _EXT_VARIABLES
 	istruc ISVARIABLESTRUC
at ivName,		dw 0
at ivFlags,		dw 0
at ivAddress,		dw 0
at ivSetup,		dw var_ext_setup
at ivArrayLast,		db 0
at ivArrayBetween,	db 0
	iend
 %xdefine IVS_MOREBYTE_NAMEHEADERS IVS_MOREBYTE_NAMEHEADERS, 0
 %endrep
%endif

%if !_LOADER
isvariablestruc "INT8CTRL", 2, 0, intr8_limit
%endif
isvariablestruc "LFSR", 4, 0, 0, 0, 0, var_lfsr_setup
isvariablestruc "RLFSR", 4, 0, 0, 0, 0, var_reverselfsr_setup
isvariablestruc "LFSRTAP", 4, 0, lfsrtap
isvariablestruc "RLFSRTOP", 1, 0, reverselfsrtop
%if _SLEEP_NEW
isvariablestruc "GREPIDLE", 1, 0, getc_repeat_idle
isvariablestruc "SREPIDLE", 1, 0, sleep_repeat_idle
isvariablestruc "SMAXDELTA", 2, 0, sleep_highest_delta
isvariablestruc "SDELTALIMIT", 2, 0, sleep_delta_limit
%endif
%if !_LOADER
isvariablestruc "DEVICESIZE", 2, ivfReadOnly, device_mcb_paragraphs
isvariablestruc "DEVICEHEADER", 4, ivfReadOnly, device_header_address
%endif
isvariablestruc "DDTEXTAND", 1, 0, dd_text_and
isvariablestruc "MACHX86", 1, ivfReadOnly, machine
isvariablestruc "MACHX87", 1, ivfReadOnly, encodedmach87
%if !_LOADER
isvariablestruc "MMT", 1, 0, maxmachinetype
%endif
isvariablestruc "DCO", 4, \
	ivfArrayOneBased | ivfArrayOptional, options, 7
isvariablestruc "DCS", 4, \
	ivfReadOnly | ivfArrayOneBased | ivfArrayOptional, startoptions, 7
isvariablestruc "DIF", 4, \
	ivfReadOnly | ivfArrayOneBased | ivfArrayOptional, internalflags, 7
%if !_LOADER
isvariablestruc "DAO", 4, \
	0, asm_options
isvariablestruc "DAS", 4, \
	ivfReadOnly, asm_startoptions
isvariablestruc "DPI", 4, \
	ivfReadOnly, psp22
%endif
isvariablestruc "DPR", 2, \
	ivfReadOnly, pspdbg
%if !_LOADER
isvariablestruc "DPP", 2, \
	ivfReadOnly, parent
isvariablestruc "DPRA", 4, \
	ivfReadOnly, psp22
%endif
isvariablestruc "DPSP", 2, \
	ivfReadOnly, pspdbg
%if !_LOADER
isvariablestruc "DPARENT", 2, \
	ivfReadOnly, parent
%endif
%if _PM
isvariablestruc "DPS", 2, \
	ivfReadOnly, 0, 0, 0, var_seldbg_setup
isvariablestruc "DPSPSEL", 2, \
	ivfReadOnly, 0, 0, 0, var_dpspsel_setup
%else
isvariablestruc "DPSPSEL", 2, \
	ivfReadOnly, pspdbg
%endif

%if !_LOADER
isvariablestruc "PPC", 4, 0, default_pp_count
isvariablestruc "TPC", 4, 0, default_tp_count
isvariablestruc "TTC", 4, 0, default_tt_count

isvariablestruc "RELIMIT", 4, 0, re_limit
isvariablestruc "RECOUNT", 4, 0, re_count
%endif
isvariablestruc "RCLIMIT", 4, 0, rc_limit
isvariablestruc "RCCOUNT", 4, 0, rc_count

isvariablestruc "RC", 2, 0, priorrc
isvariablestruc "ERC", 2, 0, erc
%if !_LOADER
isvariablestruc "QQCODE", 1, 0, qqtermcode
isvariablestruc "TERMCODE", 2, 0, usertermcode
%endif

%if _PM
 %assign var_addr_offset 4
%else
 %assign var_addr_offset 2
%endif

%if !_LOADER
isvariablestruc "AAO", var_addr_offset, 0, a_addr
isvariablestruc "AAS", 2, 0, a_addr + saSegSel
%endif
isvariablestruc "ADO", var_addr_offset, 0, d_addr
isvariablestruc "ADS", 2, 0, d_addr + saSegSel
%if !_LOADER
isvariablestruc "ABO", var_addr_offset, 0, behind_r_u_addr
isvariablestruc "ABS", 2, 0, behind_r_u_addr + saSegSel
isvariablestruc "AUO", var_addr_offset, 0, u_addr
isvariablestruc "AUS", 2, 0, u_addr + saSegSel
%endif
isvariablestruc "AEO", var_addr_offset, 0, e_addr
isvariablestruc "AES", 2, 0, e_addr + saSegSel
%if _DSTRINGS
isvariablestruc "AZO", var_addr_offset, 0, dz_addr
isvariablestruc "AZS", 2, 0, dz_addr + saSegSel
isvariablestruc "ACO", var_addr_offset, 0, dcpm_addr
isvariablestruc "ACS", 2, 0, dcpm_addr + saSegSel
isvariablestruc "APO", var_addr_offset, 0, dcount_addr
isvariablestruc "APS", 2, 0, dcount_addr + saSegSel
isvariablestruc "AWO", var_addr_offset, 0, dwcount_addr
isvariablestruc "AWS", 2, 0, dwcount_addr + saSegSel
%endif
%if _DX
isvariablestruc "AXO", 4, 0, x_addr
%endif

isvariablestruc "DSR", 1, 0, serial_rows
isvariablestruc "DSC", 1, 0, serial_columns
isvariablestruc "DST", 1, 0, serial_keep_timeout
%if _USE_TX_FIFO
isvariablestruc "DSF", 1, 0, serial_fifo_size
%endif
isvariablestruc "DSPVI", 1, 0,		 serial_var_intnum
isvariablestruc "DSPUI", 1, ivfReadOnly, serial_use_intnum
isvariablestruc "DSPVS", 1, 0,		 serial_var_params
isvariablestruc "DSPUS", 1, ivfReadOnly, serial_use_params
isvariablestruc "DSPVF", 1, 0,		 serial_var_fifo
isvariablestruc "DSPUF", 1, ivfReadOnly, serial_use_fifo
isvariablestruc "DSPVP", 2, 0,		 serial_var_baseport
isvariablestruc "DSPUP", 2, ivfReadOnly, serial_use_baseport
isvariablestruc "DSPVD", 2, 0,		 serial_var_dl
isvariablestruc "DSPUD", 2, ivfReadOnly, serial_use_dl
isvariablestruc "DSPVM", 2, 0,		 serial_var_irqmask
isvariablestruc "DSPUM", 2, ivfReadOnly, serial_use_irqmask

%if _40COLUMNS
isvariablestruc "IOCLINE", 1, 0, io_columns_getline
	; IOCLINE must be before IOC as otherwise "IOCLINE" would get
	;  misdetected as "IOC" + "L" separator + junk
%endif
isvariablestruc "IOR", 1, 0, io_rows
isvariablestruc "IOC", 1, 0, io_columns
isvariablestruc "IOS", 2, 0, io_start_buffer
isvariablestruc "IOE", 2, 0, io_end_buffer
isvariablestruc "IOL", 2, 0, io_levels
isvariablestruc "IOF", 2, 0, io_flags
isvariablestruc "IOI", 2, ivfReadOnly, 0, 0, 0, var_ioi_setup
isvariablestruc "IOK", 2, ivfReadOnly, 0, 0, 0, var_iok_setup

%if _CATCHINT2D
isvariablestruc "AMISNUM", 1, ivfReadOnly, amis_multiplex_number
isvariablestruc "TRYAMISNUM", 1, 0, try_amis_multiplex_number
%endif

isvariablestruc "HHRESULT", 4, 0, hhresult
%if _PM
isvariablestruc "DARESULT", 2, 0, daresult
%endif
%if _EMS
isvariablestruc "XARESULT", 2, 0, xaresult
%endif
%if _COUNT || _SCOUNT
isvariablestruc "COUNT", 4, 0, count_var
%endif
%if _RH
isvariablestruc "RHCOUNT", 2, ivfReadOnly, 0, 0, 0, var_rhcount_setup
%endif

%if _DEBUG1
isvariablestruc "TRC", 2, ivfArrayOptional, \
	test_records_Readmem + 4, 15, 4
isvariablestruc "TRA", 4, ivfArrayOptional, \
	test_records_Readmem, 15, 2
isvariablestruc "TWC", 2, ivfArrayOptional, \
	test_records_Writemem + 4, 15, 4
isvariablestruc "TWA", 4, ivfArrayOptional, \
	test_records_Writemem, 15, 2
isvariablestruc "TLC", 2, ivfArrayOptional, \
	test_records_getLinear + 4, 15, 4
isvariablestruc "TLA", 4, ivfArrayOptional, \
	test_records_getLinear, 15, 2
isvariablestruc "TSC", 2, ivfArrayOptional, \
	test_records_getSegmented + 4, 15, 4
isvariablestruc "TSA", 4, ivfArrayOptional, \
	test_records_getSegmented, 15, 2
isvariablestruc "TRV", 1, 0, \
	test_readmem_value
%endif


%if !_LOADER
%if _PM
isvariablestruc "SRO", 4, ivfArrayOptional | ivfReadOnly, \
	search_results, 15, 2
isvariablestruc "SRS", 2, ivfArrayOptional | ivfReadOnly, \
	search_results + 4, 15, 4
%else
isvariablestruc "SRO", 2, ivfArrayOptional | ivfReadOnly, \
	search_results, 15, 2
isvariablestruc "SRS", 2, ivfArrayOptional | ivfReadOnly, \
	search_results + 2, 15, 2
%endif
isvariablestruc "SRC", 4, ivfReadOnly, \
	sscounter
%endif

isvariablestruc "RI", 2, ivfSeparatorSpecial | ivfReadOnly, \
	0, 255, 0, var_ri_setup

%if _MMXSUPP
isvariablestruc "MM", 4, ivfSeparatorSpecial | ivfReadOnly, \
	0, 7, 0, var_mm_setup
%endif

%if _DEBUG3
isvariablestruc "MT", 4, 0, 0, 1, 0, var_mt_setup
%endif

%if _INPUT_FILE_BOOT || _INPUT_FILE_HANDLES
isvariablestruc "YSF", 2, ivfArrayOptional, \
	0, 15, 0, var_ysf_setup
 %if _INPUT_FILE_HANDLES
isvariablestruc "YHF", 2, ivfArrayOptional, \
	0, 15, 0, var_ysf_setup
 %endif
 %if _INPUT_FILE_BOOT
isvariablestruc "YBF", 2, ivfArrayOptional, \
	0, 15, 0, var_ysf_setup
 %endif
%endif

%if _ACCESS_VARIABLES_AMOUNT
isvariablestruc "READADR", 4, ivfReadOnly | ivfArrayOptional, \
	reading_access_variables, _ACCESS_VARIABLES_AMOUNT - 1, 4
isvariablestruc "READLEN", 4, ivfReadOnly | ivfArrayOptional, \
	reading_access_variables + 4, _ACCESS_VARIABLES_AMOUNT - 1, 4
isvariablestruc "WRITADR", 4, ivfReadOnly | ivfArrayOptional, \
	writing_access_variables, _ACCESS_VARIABLES_AMOUNT - 1, 4
isvariablestruc "WRITLEN", 4, ivfReadOnly | ivfArrayOptional, \
	writing_access_variables + 4, _ACCESS_VARIABLES_AMOUNT - 1, 4
%endif

%if !_LOADER
%if _PSPVARIABLES
 %if _PM
isvariablestruc "PSPS", 2, ivfReadOnly, 0, 0, 0, var_psps_setup
isvariablestruc "PSPSEL", 2, ivfReadOnly, 0, 0, 0, var_psps_setup
 %else
isvariablestruc "PSPS", 2, ivfReadOnly, pspdbe
isvariablestruc "PSPSEL", 2, ivfReadOnly, pspdbe
 %endif
isvariablestruc "PSP", 2, ivfReadOnly, pspdbe
isvariablestruc "PPR", 2, ivfReadOnly, 0, 0, 0, var_ppr_setup
isvariablestruc "PPI", 4, ivfReadOnly, 0, 0, 0, var_ppi_setup
isvariablestruc "PARENT", 2, ivfReadOnly, 0, 0, 0, var_ppr_setup
isvariablestruc "PRA", 4, ivfReadOnly, 0, 0, 0, var_ppi_setup
%endif
%endif

%if _BOOTLDR
isvariablestruc "BOOTUNITFL", 1,  0, 0, 255, 0, var_bootunitflags_setup
isvariablestruc "BOOTLDPUNIT", 1, ivfReadOnly, 0, 0, 0, var_bootldpunit_setup
isvariablestruc "BOOTSDPUNIT", 1, ivfReadOnly, 0, 0, 0, var_bootsdpunit_setup
 %if _INPUT_FILE_BOOT
isvariablestruc "BOOTYDPUNIT", 1, ivfReadOnly, 0, 0, 0, var_bootydpunit_setup
 %endif
isvariablestruc "BOOTLDPPART", 4, ivfReadOnly, 0, 0, 0, var_bootldppart_setup
isvariablestruc "BOOTSDPPART", 4, ivfReadOnly, 0, 0, 0, var_bootsdppart_setup
 %if _INPUT_FILE_BOOT
isvariablestruc "BOOTYDPPART", 4, ivfReadOnly, 0, 0, 0, var_bootydppart_setup
 %endif
%endif

%if !_LOADER
isvariablestruc "TRYDEBUGNUM", 1, 0, try_debugger_amis_multiplex_number
isvariablestruc "DEBUGFUNC", 2, ivfReadOnly, debuggerfunction
%if _MCLOPT
isvariablestruc "MASTERPICBASE", 1, 0, master_pic_base
%endif
%endif

isvariablestruc "DSTACKLEN", 2, ivfReadOnly, entryseg_size
isvariablestruc "DSTACKSEG", 2, ivfReadOnly, pspdbg
isvariablestruc "DENTRYLEN", 2, ivfReadOnly, entryseg_size
isvariablestruc "DENTRYSEG", 2, ivfReadOnly, pspdbg
isvariablestruc "DCODE1LEN", 2, ivfReadOnly, code_size
isvariablestruc "DCODE1SEG", 2, ivfReadOnly, code_seg
%if _DUALCODE
isvariablestruc "DCODE2LEN", 2, ivfReadOnly, code2_size
isvariablestruc "DCODE2SEG", 2, ivfReadOnly, code2_seg
%endif
%if _MESSAGESEGMENT
isvariablestruc "DMESSAGELEN", 2, ivfReadOnly, messageseg_size
isvariablestruc "DMESSAGESEG", 2, ivfReadOnly, messageseg
%endif
isvariablestruc "DAUXBUFLEN", 2, ivfReadOnly, auxbuff_current_size
%if _PM
isvariablestruc "DAUXBUFSEG", 2, ivfReadOnly, auxbuff_segorsel + soaSegment
 %if _HISTORY_SEPARATE_FIXED && _HISTORY
isvariablestruc "DHISBUFLEN", 2, ivfReadOnly, historyseg_size
isvariablestruc "DHISBUFSEG", 2, ivfReadOnly, history.segorsel + soaSegment
 %endif
%else
isvariablestruc "DAUXBUFSEG", 2, ivfReadOnly, auxbuff_segorsel
 %if _HISTORY_SEPARATE_FIXED && _HISTORY
isvariablestruc "DHISBUFLEN", 2, ivfReadOnly, historyseg_size
isvariablestruc "DHISBUFSEG", 2, ivfReadOnly, history.segorsel
 %endif
%endif
 %if _IMMASM
isvariablestruc "DIMMSEG", 2, ivfReadOnly, immseg
 %endif
 %if _EXTENSIONS
isvariablestruc "DEXTLEN", 2, ivfReadOnly, extseg_size
isvariablestruc "DEXTUSED", 2, ivfReadOnly, extseg_used
	; problem: is not currently effective value if to reclaim
isvariablestruc "DEXTSEG", 2, ivfReadOnly, extseg
isvariablestruc "DEXTDATALEN", 2, ivfReadOnly, extdata_size
isvariablestruc "DEXTDATAUSED", 2, ivfReadOnly, extdata_used
	; problem: is not currently effective value if to reclaim
isvariablestruc "DEXTDATAOFS", 2, ivfReadOnly, extdata
 %endif
%if _APP_ENV_SIZE || _DEV_ENV_SIZE || _BOOT_ENV_SIZE
isvariablestruc "DENVLEN", 2, ivfReadOnly, env_size
isvariablestruc "DENVSEG", 2, ivfReadOnly, envseg
%endif
isvariablestruc "DALLOCSIZE", 2, ivfReadOnly, alloc_size
isvariablestruc "DALLOCSEG", 2, ivfReadOnly, alloc_seg

%if _PM
isvariablestruc "DSTACKSEL", 2, ivfReadOnly, dssel
isvariablestruc "DENTRYSEL", 2, ivfReadOnly, cssel
isvariablestruc "DCODE1SEL", 2, ivfReadOnly, code_sel
 %if _DUALCODE
isvariablestruc "DCODE2SEL", 2, ivfReadOnly, code2_sel
 %endif
%if _MESSAGESEGMENT
isvariablestruc "DMESSAGESEL", 2, ivfReadOnly, messagesel
%endif
isvariablestruc "DAUXBUFSEL", 2, ivfReadOnly, auxbuff_segorsel + soaSelector
 %if _HISTORY_SEPARATE_FIXED && _HISTORY
isvariablestruc "DHISBUFSEL", 2, ivfReadOnly, history.segorsel + soaSelector
 %endif
isvariablestruc "DSCRATCHSEL", 2, ivfReadOnly, scratchsel
 %if _SYMBOLIC
isvariablestruc "DSYM1SEL", 2, ivfReadOnly, symsel1
isvariablestruc "DSYM2SEL", 2, ivfReadOnly, symsel2
 %endif
 %if _IMMASM
isvariablestruc "DIMMSEL", 2, ivfReadOnly, immsel
 %endif
 %if _EXTENSIONS
isvariablestruc "DEXTCSSEL", 2, ivfReadOnly, extcssel
isvariablestruc "DEXTDSSEL", 2, ivfReadOnly, extdssel
 %endif
%endif

isvariablestruc "DEFAULTDLEN", 2, 0, dd_default_length
isvariablestruc "DEFAULTDLINES", 2, 0, dd_default_lines
%if !_LOADER
isvariablestruc "DEFAULTULEN", 2, 0, uu_default_length
isvariablestruc "DEFAULTULINES", 2, 0, uu_default_lines
%endif

%if _PM
isvariablestruc "CIP", 2, 0, 0, 0, 0, var_cip_setup
isvariablestruc "CSP", 2, 0, 0, 0, 0, var_csp_setup
%else
isvariablestruc "CIP", 2, 0, reg_eip
isvariablestruc "CSP", 2, 0, reg_esp
%endif

%if _LINK
isvariablestruc "LINKOFFSET", 2, ivfReadOnly, linkinfoaddress
isvariablestruc "LINKPOINTER", 4, ivfReadOnly, linkinfoaddress
isvariablestruc "LINKSEG", 2, ivfReadOnly, linkseg
 %if _PM
isvariablestruc "LINKSEL", 2, ivfReadOnly, linksel
 %endif
%endif

%if _PM && _DEBUG_PM_ENTRY
isvariablestruc "DEBUGPMET", 4, 0, debug_pm_entry_total
isvariablestruc "DEBUGPMEH", 4, 0, debug_pm_entry_esph
isvariablestruc "DEBUGPMEB", 4, 0, debug_pm_entry_b
%endif

isvariable_struc_morebyte_list_end:
isvariable_struc_list_end:

	align 2, db 0
interrupt_var:	dd 0
	align 2, db 0
ioi_var:	dw 0
iok_var:	dw 0
%if _PM
	align 2, db 0
seldbg:	dw 0
	align 2, db 0
dpspsel:	dw 0
%endif
%if _PSPVARIABLES
	align 2, db 0
 %if _PM
psp_selector:	dw 0
 %endif
psp_parent:	dw 0
psp_pra:	dd 0
%endif

	align 2, db 0
isvariable_morebyte_nameheaders:
.ext:
	dw IVS_MOREBYTE_NAMEHEADERS
.end:
%if IVS_HAVE_ONEBYTE && ! IVS_SINGLE_ONEBYTE
isvariable_onebyte_names:
	db IVS_ONEBYTE_NAMES
.end:
%endif

isvariablestrings ISVARIABLESTRINGS


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif
isvariable_code:
		; INP:	ax = array index (0-based)
		;	di = 0
		;	cl = default size of variable (1..4)
		;	ch = length of variable name
		; CHG:	si, ax, dx
		; OUT:	NC if valid,
		;	 bx -> var, di = 0 or di -> mask
		;	 cl = size of variable (1..4)
		;	 ch = length of variable name

%if _EXTENSIONS && _EXT_VARIABLES
var_ext_setup: section_of_function
	xchg di, cx
	mov cx, word [bx + ivAddress]
	jcxz .cy
%if _EXPRDUALCODE && _DUALCODE
	nearcall transfer_ext_cx_nearcall_from_code2

	usesection lDEBUG_CODE
transfer_ext_cx_nearcall_from_code2: section_of_function
	add sp, 6
%endif
	jmp transfer_ext_cx

%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%endif

var_ext_setup.cy:
	xchg di, cx
	stc
	retn

var_ext_setup_done: section_of_function
	pop ax
	pop ax
	pop ax
	retn
%endif


%if _PM
var_cip_setup: section_of_function
	mov bx, word [reg_cs]
	nearcall test_d_b_bit
	mov bx, reg_eip
@@:
	jz .ret
	inc cx
	inc cx			; = 4
.ret:
	clc			; (NC)
	retn

var_csp_setup: section_of_function
	mov bx, word [reg_ss]
	nearcall test_d_b_bit
	mov bx, reg_esp
	jmp @B
%endif


var_lfsr_setup: section_of_function
	mov bx, lfsr
	shr word [bx + 2], 1
	rcr word [bx], 1
	jnc .ret		; if not to tap --> (NC)
	mov ax, word [lfsrtap]
	xor word [bx], ax
	mov ax, word [lfsrtap + 2]
	xor word [bx + 2], ax	; (NC)
.ret:
	retn


var_reverselfsr_setup: section_of_function
	mov bx, reverselfsr
	xchg ax, cx		; preserve original cx
	mov cl, byte [reverselfsrtop]
	mov si, 1		; register for shift mask
	cmp cl, 16		; fits in low 16 bits ?
	jb .below_16		; yes -->
	sub cl, 16		; get bit within high word
	cmp cl, 16		; beyond maximum ?
	jb @F
	mov cl, 15		; yes, clamp to bit 31 (for now)
@@:
	shl si, cl		; shift to get a bitmask
	xchg cx, ax		; restore the original cx
	test word [bx + 2], si	; is top bit set ?
	lahf			; preserve ZF
	not si			; invert mask to allow clearing
	and word [bx + 2], si	; clear the bit if it was set
	mov di, -1		; si:di = mask what to clear in taps
	jmp @F

.below_16:
	shl si, cl		; shift to get a bitmask
	xchg cx, ax		; restore the original cx
	test word [bx], si	; is top bit set ?
	lahf			; preserve ZF
	not si			; invert mask to allow clearing
	and word [bx], si	; clear the bit if it was set
	mov di, si
	mov si, -1		; si:di = mask what to clear in taps
@@:
	sahf			; restore ZF
	xchg ax, di		; si:ax = mask what to clear in taps
	jz .justshift		; if not to tap, just shift --> (NC)
	call .justshift		; also shift, but return to us afterwards
	and ax, word [lfsrtap]
	and si, word [lfsrtap + 2]
				; get the taps (highest bit cleared)
	stc			; lowest bit will get set to 1
	rcl ax, 1
	rcl si, 1		; shift the taps to create reverse taps
	xor word [bx], ax
	xor word [bx + 2], si	; tap (NC)
	retn

.justshift:
	shl word [bx], 1
	rcl word [bx + 2], 1
	xor di, di		; restore di = 0
				; also: set NC if return from setup function
	retn


%if _PM
var_dpspsel_setup: section_of_function
	mov bx, dpspsel
	mov word [bx], ss
	clc
	retn

var_seldbg_setup: section_of_function
	mov bx, seldbg
	and word [bx], byte 0
	nearcall ispm
	jnz @F
	mov word [bx], ds
@@:
	clc
	retn
%endif

%if _DEBUG3
var_mt_setup: section_of_function
	mov bx, ax
	add bx, bx
	add bx, bx
	mov di, bx
	add bx, mtest_regs
	add di, masks_test
	clc
	retn
%endif


%if _RH
		; INP:	ax = array index (0-based), di = 0
		;	cl = default size of variable (1..4)
		; CHG:	si, ax
		; OUT:	NC if valid,
		;	 bx -> var, di = 0 or di -> mask
		;	 cl = size of variable (1..4)
var_rhcount_setup: section_of_function
	push cx
%if _PM
	mov di, word [auxbuff_switchbuffer_size]
%else
	xor di, di
%endif
	nearcall silence_get_start.have_di
	nearcall silence_count_zeroes

	xchg bx, ax
	mov bx, rhcount
	mov word [bx], ax
	pop cx
	push ss
	pop es
	xor di, di		; NC
	retn
%endif


var_ioi_setup: section_of_function
	setopt [internalflags3], dif3_input_terminal_override
	nearcall peekc
	clropt [internalflags3], dif3_input_terminal_override
	mov bx, ioi_var
	mov word [bx], ax
	clc
	retn

var_iok_setup: section_of_function
	setopt [internalflags3], dif3_input_terminal_override
	nearcall getc
	clropt [internalflags3], dif3_input_terminal_override
	mov bx, iok_var
	mov word [bx], ax
	clc
	retn

%if !_LOADER
%if _PSPVARIABLES
%if _PM
var_psps_setup: section_of_function
	mov bx, psp_selector
	nearcall var_get_psp_segment
	jc @F
	nearcall ispm
	jnz @F
	push bx
	xchg ax, bx
	mov ax, 0002h
	int 31h
	pop bx
@@:
	mov word [bx], ax
	clc
	retn
%endif


var_ppr_setup: section_of_function
 %if _PM
	 sub sp, 8
	call save_scratchsel
 %endif
	mov bx, psp_parent
 	xor ax, ax
	mov word [bx], ax
	nearcall var_get_psp_selector
	jc @F
	mov ax, word [es:16h]
	mov word [bx], ax		; retrieve parent process
@@:
	push ss
	pop es
 %if _PM
	call restore_scratchsel
		; This is not really needed but does not hurt either.
 		;  getsegmented is used above, but only from PM.
 		;  This is assumed not to switch modes.
	nearcall resetmode		; reset mode if we switched
 %endif
	clc
	retn


var_ppi_setup: section_of_function
 %if _PM
	 sub sp, 8
	call save_scratchsel
 %endif
	mov bx, psp_pra
 	xor ax, ax
	mov word [bx], ax
	mov word [bx + 2], ax
	nearcall var_get_psp_selector
	jc @B
	mov ax, word [es:TPIV]
	mov word [bx], ax
	mov ax, word [es:TPIV + 2]
	mov word [bx + 2], ax		; retrieve Int22 address
	jmp @B


var_get_psp_segment: section_of_function
	mov ax, word [pspdbe]
	inc ax				; FFFFh ?
	jz short .pspvar_psp_invalid	; yes, invalid -->
	dec ax				; 0 ?
	jz short .pspvar_psp_invalid	; yes, invalid -->
	clc
	retn

.pspvar_psp_invalid:
	xor ax, ax
	stc
	retn


var_get_psp_selector: section_of_function
	nearcall var_get_psp_segment
	jc .pspvar_psp_invalid
 %if _PM
	nearcall ispm
	jnz short .pspvar_rm
	push bx
	_386_o32		; push edx
	push dx
	push cx
	xor dx, dx
	mov cx, 4
.pspvar_shift:
	shl ax, 1
	rcl dx, 1
	loop .pspvar_shift		; dx:ax = PSP segment << 4
	nearcall getsegmented
		; getsegmented is assumed not to switch modes (see below).
	jc short .pspvar_error		; (shouldn't happen)
	_386_o32
	test dx, dx		; test edx, edx
	jnz short .pspvar_error		; (assumed not to happen)
	nearcall setrmlimit
	pop cx
	_386_o32
	pop dx			; pop edx
	xchg ax, bx			; ax = selector
	pop bx
.pspvar_rm:
 %endif
	mov es, ax			; es = segment/selector
	cmp word [es:0], 20CDh		; int 20h opcode ?
	jne short .pspvar_psp_invalid	; no, invalid -->
	clc
	retn

.pspvar_psp_invalid:
	stc
	retn

.pspvar_error:
	xor si, si
	jmp error_expr
%endif
%endif


%if _BOOTLDR
var_bootldpunit_setup: section_of_function
	mov bx, loaddata_loadedfrom - LOADDATA \
			+ bsBPB + ebpbNew + bpbnBootUnit
	jmp @F

var_bootsdpunit_setup: section_of_function
	mov bx, load_data - LOADDATA2 \
			+ bsBPB + ebpbNew + bpbnBootUnit
	jmp @F

%if _INPUT_FILE_BOOT
var_bootydpunit_setup: section_of_function
	testopt [internalflags2], dif2_input_file_boot
	jz .retc

	push dx
	mov ax, LOAD_INPUT_FILE_SIZE
	mul word [load_input_file.active]
	pop dx

	xchg ax, bx
	lea bx, [load_input_file + bx - LOADDATA3 \
			+ bsBPB + ebpbNew + bpbnBootUnit]
	jmp @F
%endif

var_bootldppart_setup: section_of_function
	mov bx, loaddata_loadedfrom - LOADDATA \
			+ bsBPB + bpbHiddenSectors
	jmp @F

var_bootsdppart_setup: section_of_function
	mov bx, load_data - LOADDATA2 \
			+ bsBPB + bpbHiddenSectors
	jmp @F

%if _INPUT_FILE_BOOT
var_bootydppart_setup: section_of_function
	testopt [internalflags2], dif2_input_file_boot
	jz .retc

	push dx
	mov ax, LOAD_INPUT_FILE_SIZE
	mul word [load_input_file.active]
	pop dx

	xchg ax, bx
	lea bx, [load_input_file + bx - LOADDATA3 \
			+ bsBPB + bpbHiddenSectors]
	jmp @F
%endif

var_bootunitflags_setup: section_of_function
	add ax, load_unit_flags
	mov bx, ax
	mov di, mask_bootunitflags
@@:
	testopt [internalflags], nodosloaded
	jnz @F				; (NC)
var_bootydpunit_setup.retc:
var_bootydppart_setup.retc:
	stc
@@:
	retn
%endif


	usesection lDEBUG_DATA_ENTRY

	align 4, db 0
masks:
	; size 4 want masks + 0
	; size 3 want masks + 1
	; size 2 want masks + 2
	; size 1 want masks + 3
	; size 0 want masks + 4
	; 4 - size = offset into masks
mask_4byte:		db 0
mask_3byte:		db 0
mask_2byte:		db 0
mask_1byte:		db 0
mask_0byte:		dd -1
mask_compoundwithzero:	db -1, -1, 0, 0
mask_compoundreg8withzero:
			db -1, -1, 0, -1
mask_ysf:		dd ~ (ifhfTestReserved1 | ifhfTestReserved2 \
				| ifhfQuietInput | ifhfQuietOutput)
%if _BOOTLDR
mask_bootunitflags:	dd ~ luf_mask_writable
%endif

%if _DEBUG3
masks_test:
mask_test_0:		dd 0AA55AA55h
mask_test_1:		dd 00FF00FFh
mtest_regs:
mtest_reg_0:		dd 0
mtest_reg_1:		dd 00110022h
%endif


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif

%if _SYMBOLIC
		; INP:	al = first character
		;	si -> next character
		; OUT:	CY if no symbol
		;	NC if symbol,
		;	 bx:dx = symbol (offset) value
		;	 al = next character behind symbol
		;	 si -> behind next character
		; CHG:	ah, bx, cx, dx
		; STT:	ss = ds = es, UP
		;
		; Note:	This invalidates the symbol table access slice.
issymbol?: section_of_function
	nearcall zz_detect_xms	; re-detect XMS if used after run (eg WHILE)

	lframe near
	lvar word,	new_si
	lenter
	lvar word,	orig_si
	 push si
	lvar word,	orig_ax
	 push ax
	xor bx, bx
	lequ 1,		flag_has_nondigit
	lequ 2,		flag_has_symbol_prefix
	lequ 4,		flag_has_linear
	lequ 8,		flag_has_offset
	lequ 16,	flag_has_base
	lequ 32,	flag_is_86m_segment
	lvar word,	flags
	 push bx
	lvar word,	main_index	; used as parameter to zz_match_symbol
	 push ax

	dec si
%if 0
	cmp al, '.'
	jne @F
	inc si			; allow dot prefix to symbol name
	mov al, byte [si]
@@:
%endif
	cmp al, '0'
	jb @F
	cmp al, '9'
	jbe .not
@@:

	mov dx, msg.sl
	nearcall isstring?
	jne @F
	lodsb
	cmp al, '.'
	jne short .error
	or byte [bp + ?flags], ?flag_has_symbol_prefix | ?flag_has_linear
	jmp .not_symbol_prefix
@@:

	mov dx, msg.symbol
	nearcall isstring?
	jne .not_symbol_prefix
	lodsb
	cmp al, '.'
	jne short .error
	or byte [bp + ?flags], ?flag_has_symbol_prefix

	mov dx, msg.linear
	nearcall isstring?
	jne @F
	lodsb
	cmp al, '.'
	jne short .error
	or byte [bp + ?flags], ?flag_has_linear
@@:

	mov dx, msg.offset
	nearcall isstring?
	jne @F
	lodsb
	cmp al, '.'
	jne short .error
	test byte [bp + ?flags], ?flag_has_linear
	jnz short .error
	or byte [bp + ?flags], ?flag_has_offset
@@:

	mov dx, msg.base
	nearcall isstring?
	jne @F
	lodsb
	cmp al, '.'
	jne short .error
	test byte [bp + ?flags], ?flag_has_linear | ?flag_has_offset
	jnz short .error
	or byte [bp + ?flags], ?flag_has_base
@@:

.not_symbol_prefix:

	xor bx, bx
	mov dx, si
.loopname:
	lodsb
	nearcall isseparator?.except_L_or_dot
	je .endname
	nearcall getexpression.lit_ishexdigit?
	jnc @F
	or byte [bp + ?flags], ?flag_has_nondigit
@@:
	inc bx
	jmp .loopname

.error:
	jmp error_expr			; does not use es

.endname:
	mov word [bp + ?new_si], si
	mov cx, bx
	test cx, cx
	jz .not

	test byte [bp + ?flags], ?flag_has_nondigit | ?flag_has_symbol_prefix
	jz .not

	mov si, dx
	push cx
	nearcall zz_hash
	pop cx

.loop:
		; ?main_index used as parameter
	 push ss
	 push dx
	 push cx

	dualcall zz_match_symbol		; ! note that possibly es != ss
	jc .not

	testopt [es:di + smFlags], smfPoison
	jz @F
	setopt [internalflags2], dif2_poison
@@:

	mov dx, word [es:di + smLinear]
	mov bx, word [es:di + smLinear + 2]
	test byte [bp + ?flags], ?flag_has_linear
	jnz .got
	test byte [bp + ?flags], ?flag_has_base
	jz .offset
.base:
		; offset=100 linear=10100 base=10000
	sub dx, word [es:di + smOffset]
	sbb bx, word [es:di + smOffset + 2]
	jmp .got

.offset:
	mov si, word [bp + ?new_si]
	dec si
	nearcall skipcomma		; does not use es
	dec si
	mov dx, msg.wrt
	push es
	 push ss
	 pop es
	nearcall isstring?		; uses es
	pop es
	jne .offset_no_wrt

	nearcall skipcomma		; does not use es

	cmp al, '$'
	jne @F

	or byte [bp + ?flags], ?flag_is_86m_segment
	nearcall skipcomma		; does not use es
@@:

	push word [hh_depth_of_single_term]
	mov dx, word [hh_depth]
	inc dx
	mov word [hh_depth_of_single_term], dx
	 push ss
	 pop es
	nearcall getexpression		; (recursive) uses es
	pop word [hh_depth_of_single_term]

		; The getexpression call may recurse into calling
		;  this function itself again. Therefore, it can
		;  invalidate the access slice. To make sure we
		;  can access the SYMMAIN entry again, reload it.
	 push word [bp + ?main_index]
	 push ax		; (reserve space, uninitialised)
	dualcall getfarpointer.main
	 pop di
	 pop es

	nearcall skipcomm0		; does not use es
	mov word [bp + ?new_si], si

	test bx, bx
	jnz short .errorj1

	mov bx, dx
%if _PM
	test byte [bp + ?flags], ?flag_is_86m_segment
	jnz .wrt_rm
	nearcall ispm			; does not use es
	jnz .wrt_rm

	mov ax, 0006h
	int 31h					; cx:dx = base
	jc short .errorj1
	xchg ax, cx				; ax:dx = base
	xchg dx, ax				; dx:ax = base
	jmp .offset_wrt

.wrt_rm:
%endif
	xor dx, dx
	mov ax, bx
	mov cx, 4
@@:
	shl ax, 1
	rcl dx, 1
	loop @B

.offset_wrt:
		; dx:ax = seg base

		; offset=100
		; linear=10100
		; seg base=8000
		; offset wrt seg=8100
	neg dx
	neg ax
	sbb dx, byte 0				; neg dx:ax
	add ax, word [es:di + smLinear]
	adc dx, word [es:di + smLinear + 2]	; linear - seg base
	xchg ax, dx				; ax:dx = value
	xchg ax, bx				; bx:dx = value
	jmp .got

.errorj1:
	jmp error_expr			; does not use es


.offset_no_wrt:
	mov dx, word [es:di + smOffset]
	mov bx, word [es:di + smOffset + 2]

.got:
	testopt [es:di + smFlags], smfBase
	jz @F
	push word [bp + ?main_index]
	pop word [sym_storage.main.based.base]
		; if called from zz_add set up base
@@:

	mov si, word [bp + ?new_si]
	dec si
	lodsb
	clc
	jmp .ret
.not:
	stc

	mov si, word [bp + ?orig_si]
	mov ax, word [bp + ?orig_ax]
.ret:
	push ss
	pop es				; reset es
	lleave
	retn
%endif


		; INP:	al = first character
		;	si-> next character
		; OUT:	CY if no variable,
		;	 al, si = unchanged
		;	NC if variable,
		;	 bx-> low word
		;	 dx-> high word
		;		(if cl <= 2 then dx-> some word in our memory)
		;		(dx != bx+2 if compound register)
		;	 cl = size of variable (1, 2, 3, 4 bytes)
		;	 ch = size of variable's name (2..13 bytes)
		;		(this is limited to ivfNameLengthLimit)
		;	 INP:si - 1 -> variable's name
		;		(can be modified from original content)
		;	 ah = 0 if a writeable variable (ie simply memory)
		;	      1 if a read-only variable (ie simply memory)
		;	      2..33 if an MMX register, see note below
		;	 al = next character behind variable
		;	 si-> behind next character
		;	 dword [di] = mask of bits that are read-only
		; CHG:	ah, bx, dx, cx, di
		; STT:	ss = ds = es, UP
		;
		; Note: For read access to (half of) an MMX register, no
		;	 special handling is necessary at all, because cl,
		;	 bx, and dx are set up to access a buffer that
		;	 contains the current value. (The value should be
		;	 read at once though, as the buffer may be shared
		;	 or become outdated otherwise.)
		;	Write access to an MMX register must be handled
		;	 specifically, however. The returned field type
		;	 in ah indicates the register number (0..7) in the
		;	 lowest three bits. The two bits above those specify
		;	 the access type, which also specifies what was read
		;	 but need not be examined by readers. The access
		;	 type must be adhered to by writers. These are the
		;	 access types:
		;	  0 zero extension from 32 bits to write all 64 bits
		;	  1 sign extension from 32 bits to write all 64 bits
		;	  2 writes only low 32 bits
		;	  3 writes only high 32 bits
		;	 (Access type 3 is the only one for which the read
		;	 buffer is initialised with the high 32 bits.)
		;
		;	As dx points to 'some word in our memory' if it
		;	 doesn't serve any purpose, it is still valid to
		;	 read the word that it points to. Particularly dx
		;	 mustn't contain 0FFFFh then, but with the current
		;	 implementation, it can also be assumed that we do
		;	 actually 'own' the word (even with a PM segment
		;	 shorter than 64 KiB the read would be allowed).
isvariable?: section_of_function
	db __TEST_IMM8		; (skip stc, NC)

		; As above but additionally:
		;
		; INP:	di -> buffer to receive variable name, 14 bytes
		; OUT:	if NC, buffer filled with all-caps ASCIZ name
.return_name: section_of_function isvariable?
	stc

	dec si
	lframe
	lenter
	lvar word, bit0_is_return_name
	 pushf
	lvar word, return_name_pointer
	 push di
%if ivfNameLengthLimit != 13
 %error Adjust code here to new ivfNameLengthLimit
%endif
	xor ax, ax
	lvar 16, namebuffer
	 push ax		; ax = 0 so that accidentally reading past
				;	  the actual buffer wouldn't match
	 push word [si+12]
	 push word [si+10]
	 push word [si+8]
	 push word [si+6]
	 push word [si+4]
	 push word [si+2]
	 push word [si]
	mov di, sp		; -> name buffer

	lvar word, fieldtype_high_flags_low
	lequ ?fieldtype_high_flags_low + 1, fieldtype
	lequ ?fieldtype_high_flags_low, flags
	push ax			; field type initialised to 0 (RW), flags too
_386	xor bx, bx		; (a flag for the 32-bit register name check)
	lvar word, length_to_add
	push ax			; = 0
	lvar word, startpointer
	push si			; -> name start
	mov si, di
	 push di

	mov cx, 16
.store:
	lodsb
	; nearcall uppercase; (isseparator? calls uppercase)
	nearcall isseparator?
	clc
	jne short .not_separator
	stc
.not_separator:
	rcr dx, 1		; dl = flags indicating separators
	stosb
	loop .store

	 pop si

	test byte [bp + ?bit0_is_return_name], 1
	jz @F

	push si
	mov cl, 14 >> 1
	mov di, word [bp + ?return_name_pointer]
	rep movsw
	pop si

@@:
	test dl, 1<<2|1<<4
	lodsw
	jz .notreg16

	call .reg16names_match	; (iff no match, --> .notreg16)
				; bx-> regs entry of (first) match
	test dl, 1<<2
	lodsw
	jz @F
	test dl, 1<<5
	jz .reg16_j
	cmp word [si - 4], "FL"
	jne .reg16_j
	cmp al, '.'
	jne .reg16_j
	dec si
	lodsw
	mov di, flagnames
	mov cx, flagbits.amount
	repne scasw
	jne .reg16_j
	mov ax, [di - (flagnames + 2) + flagbits]
	mov bx, flagvaron
	test ax, word [reg_efl]
	jnz .flagon
	inc bx
.flagon:
	mov cx, (5 << 8) | 1	; 5-byte name, 1-byte variable
	inc byte [bp + ?fieldtype]
				; = 1 (RO)
	jmp .return_success_var32_set_dx

.reg16_j:
	jmp .reg16		; iff single match -->

@@:
	cmp ax, "00"
	je .compoundwithzero

		; Check for a second 16-bit register name
		;  (ie check for a compound register name)
	call .reg16names_match	; (iff no match, --> .notreg16)
				; bx-> regs entry of second match
	xchg dx, ax		; dx-> regs entry of first match
	mov cx, 4<<8|4
.return_success:		; cx, bx, dx, ?fieldtype set
	xor ax, ax
	mov al, cl
	neg ax
	add ax, masks + 4	; 4 - size = offset into masks
	xchg di, ax		; di -> mask
	cmp byte [bp + ?fieldtype], 1
	jne @F
	mov di, mask_0byte	; di -> mask
@@:
.return_success_di:
	xor ax, ax
	mov al, ch		; ax = length

	test byte [bp + ?bit0_is_return_name], 1
	jz @F

	mov si, word [bp + ?return_name_pointer]
	add si, ax
	mov byte [si], ah	; zero-terminate
@@:

	pop si			; ?startpointer
	testopt [bp + ?flags], 1
	jz @F
	pop ax			; get length to add to start
	push ax			; fill stack slot again
@@:
	add si, ax		; -> behind name (should NC)
	pop ax			; discard ?length_to_add
	clc			; (NC)
.return_ax_frame_lodsb:
	pop ax			; ah = ?type
	lleave code
	lodsb
.retn:
	retn


.compoundwithzero:
	mov di, mask_compoundwithzero
				; only allow writing upper word
	mov cx, 4 << 8 | 4	; 4-byte name, 4-byte variable
.return_compoundwithzero:
	mov dx, bx		; -> word to use as upper word
	mov bx, mask_compoundwithzero + 2
				; -> (constant) word that is zero, as lower word
		; This depends on the contents of mask_compoundwithzero.
	jmp .return_success_di


		; INP:	ax = capitalised candidate register name
		;	ch = 0
		;	dx, si, bx, etc set up for later checks
		; OUT:	Iff match,
		;	 bx-> associated regs entry (dword-aligned)
		;	 ax = INP:bx
		;	Else,
		;	 returns to .notreg16
		;	 bx left unchanged on 386 systems
		; CHG:	cl, di, bx, ax
		;
		; Note:	The 32-bit register name check depends on the
		;	 fact that the low two bits of bx are set to
		;	 zero on a match, which is true because regs
		;	 is dword-aligned.
		;	It also depends on bx being left unchanged on
		;	 a mismatch, which is the case unless the
		;	 non-386 additional FS,GS filtering occurs.
.reg16names_match:
	mov di, reg16names
	mov cl, 16
	repne scasw
	jne short .notreg16_pop	; no match -->
	add di, di
	xchg ax, bx		; (returned for compound register name match)
	lea bx, [di -2*(reg16names+DATASECTIONFIXUP+2) +regs]
				; -> regs entry
_386	retn
				; cx = number of remaining reg16names
_no386	shr cx, 1		; cx = number of remaining reg16names pairs
				;    = 1 iff exactly the IP,FL pair remaining
				;	  (ie matched one of FS,GS)
_no386	loop .retn		; iff cx != 1, return the match -->
			; on non-386 systems, FS,GS matches fall through here
.notreg16_pop:
	pop ax			; (discard near return address)
.notreg16:
		; Check for a 32-bit register name
_386	test dl, 1<<3
_386	jz short .notreg32

_386	lea si, [bp+?namebuffer]
_386	lodsb
_386	shr bl, 1		; CY iff second entry during same call
				;  (in that case, al contains 'E')
_386	sbb al, 'E'		; possibly an 'E' register ? (on first entry)
_386	lodsw
_386	jne short .notreg32	; no --> (or after second entry)
_386	inc bx			; prepare flag for second entry
				;  (this requires regs to be dword-aligned!)
_386	cmp ah, 'S'		; candidate segment register ?
_386	je short .notreg32	; yes, skip check (disallow match) -->

_386	call .reg16names_match	; (iff no match, --> .notreg16 (second entry))
				; bx-> regs entry of match
_386	mov cx, 3<<8|4
_386	jmp short .return_success_var32_set_dx

.notreg32:
		; Check for an 8-bit register name
	test dl, 1 << 2 | 1 << 4
	jz .notreg8

	lea si, [bp+?namebuffer]
	lodsw
	mov di, reg8names
	mov cl, 8
	repne scasw
	jne short .notreg8
				; cx = cl = number of remaining reg8names
	and cl, 1		; cx = cl = 1 iff an xL register, else 0
	lea bx, [di-reg8names-2+regs-1]
				; bx-> reg_eax-1 if AL, reg_eax+1 if AH, etc
	add bx, cx		; bx-> reg_eax   if AL, reg_eax+1 if AH, etc
	mov cl, 1

	test dl, 1 << 2
	jnz .got2bytename

	lodsw
	cmp ax, "00"
	je .compoundreg8withzero

	mov di, reg16names
	mov cl, 16
	repne scasw
	jne short .notreg8	; no match -->
	add di, di
	xchg dx, bx
	lea bx, [di -2*(reg16names+DATASECTIONFIXUP+2) +regs]
				; -> regs entry
				; bx-> regs entry of second match
				; dx-> regs entry of first match
	mov cx, 4 << 8 | 3	; 4-byte name, 3-byte variable
	jmp .return_success	; cx, bx, dx, ?fieldtype set

.compoundreg8withzero:
	mov di, mask_compoundreg8withzero
				; only allow writing low byte of upper word
	mov cx, 4 << 8 | 3	; 4-byte name, 3-byte variable
	jmp .return_compoundwithzero

.reg16:
	mov cl, 2
.got2bytename:
.got2bytename_var32_set_dx:
	mov ch, 2
.return_success_var32_set_dx:
	lea dx, [bx+2]		; (irrelevant to 8-/16-bit register return)
%define .return_success_j .return_success_j1
%[.return_success_j]:
	jmp .return_success

.notreg8:

	mov di, isvariable_morebyte_nameheaders

.loop:
	and word [bp + ?fieldtype_high_flags_low], 0
					; clear in case earlier iteration set it

	mov ax, word [bp + ?namebuffer]
	mov cx, isvariable_morebyte_nameheaders.end
	cmp di, cx
	jae @F
	sub cx, di
	shr cx, 1			; = how many headers remaining
	repne scasw
	je .check_morebyte
%if IVS_HAVE_ONEBYTE
	jmp .next_no_pop

@@:
 %if IVS_SINGLE_ONEBYTE
	inc cx
	cmp di, cx			; iteration after one byte var check ?
	jae @F				; yes, end -->
	inc di				; remember we did the one byte check
	cmp al, IVS_SINGLE_ONEBYTE_NAME
	je .check_onebyte
 %else
	mov cx, isvariable_onebyte_names.end
	sub cx, di			; = how many names remaining
	jbe @F				; if none -->
	repne scasb
	je .check_onebyte
 %endif
%endif
	db __TEST_IMM8			; (skip pop)
.next:
	pop di
.next_no_pop:
	jmp .loop

@@:
.end:
.return_failure:
	pop si			; ?startpointer
	pop ax			; ?length_to_add
	stc
	jmp .return_ax_frame_lodsb


%if IVS_HAVE_ONEBYTE
.check_onebyte:
	push di
 %if IVS_SINGLE_ONEBYTE
	mov bx, isvariable_struc_onebyte_list
 %else
	sub di, isvariable_onebyte_names + 1
					; = index
%if ISVARIABLESTRUC_size == 10
	add di, di			; = index * 2
	mov bx, di			; = index * 2
	add di, di			; = index * 4
	add di, di			; = index * 8
	add di, bx			; = index * 10
%else
 %error Unexpected structure size
%endif
	lea bx, [di + isvariable_struc_onebyte_list]
 %endif
	lea si, [bp + ?namebuffer + 1]	; -> buffered string (allcaps)
	xor cx, cx			; = 0, gets incremented next
	jmp @F
%endif

.check_morebyte:
	push di
	sub di, isvariable_morebyte_nameheaders + 2
					; = index * 2
%if ISVARIABLESTRUC_size == 10
	mov bx, di			; = index * 2
	add di, di			; = index * 4
	add di, di			; = index * 8
	add di, bx			; = index * 10
%else
 %error Unexpected structure size
%endif
	lea bx, [di + isvariable_struc_morebyte_list]
	mov cx, word [bx + ivFlags]
	and cx, ivfNameLengthMask	; cx = length
	mov di, word [bx + ivName]	; -> name
	lea si, [bp + ?namebuffer + 2]	; -> buffered string (allcaps)
	cmp al, al		; ZR (in case remaining length is zero)
	push cx
	repe cmpsb		; compare
	pop cx
	jne .next		; if not same -->
	inc cx			; increment twice
@@:
	inc cx			; increment once
	lodsb			; get next byte in buffer
	cmp byte [bx + ivArrayLast], 0
	je .notarray

.array:
	cmp al, '('
	jne .array_not_index_expression	; not index expression -->

.array_index_expression:
	mov si, word [bp + ?startpointer]
	add si, cx		; -> at opening parens
	inc si			; -> behind opening parens
	lodsb
	push dx
	push bx
	nearcall getexpression
	push ax
	and ah, 3Fh
	cmp ah, 8		; from checksignificantbits
	pop ax
	mov dh, 0
	mov di, dx		; di = index
	pop bx
	pop dx
	ja short .next		; more than 8 significant bits -->
	nearcall skipwh0
	cmp al, ')'		; insure closing parens
	jne short .error_j3

	push si
	sub si, word [bp + ?startpointer]
				; = how much to skip
	mov word [bp + ?length_to_add], si
	pop si
	setopt [bp + ?flags], 1

	test byte [bp + ?bit0_is_return_name], 1
	jz .array_no_return_name

	xchg ax, di
	mov di, word [bp + ?return_name_pointer]
	add di, cx		; -> behind name
	cmp byte [bx + ivArrayLast], 0Fh
	ja @F			; if >= 10h is a valid index -->
	nearcall hexnyb		; expand to single digit
	jmp @FF
@@:
	nearcall hexbyte	; expand to two digits
@@:
	xchg ax, di		; restore di = index

.array_no_return_name:
	inc cx			; make space for one digit
	cmp byte [bx + ivArrayLast], 0Fh
	jna @F			; if maximum index <= 0Fh -->
	inc cx			; make space for another digit
@@:
	jmp .array_index_expression_common

.array_not_index_expression:
	nearcall isseparator?	; name ends ?
	jne @F			; no -->
	testopt [bx + ivFlags], ivfArrayOptional
				; is the index optional ?
	jnz .arrayfirst		; yes, handle as if first -->
.next_j1:
	jmp .next		; no, mismatch -->

.error_j3:
	jmp error_expr

@@:
	nearcall getnyb
	jc .next_j1
	cbw			; ax = first nybble of index
	xchg di, ax		; di = first nybble of index
	lodsb
	 testopt [bx + ivFlags], ivfSeparatorSpecial
	 jz @F
	cmp byte [bx + ivArrayLast], 0Fh
				; highest > 0Fh ?
	jna .gotseparatorspecial; no -->
	nearcall getnyb
	jc .gotseparatorspecial	; not hexit, treat as separator -->
	jmp .have_second_digit	; have al == 0..0Fh

@@:
	nearcall isseparator?	; single digit ?
	je @F			; yes -->
	cmp byte [bx + ivArrayLast], 0Fh
				; highest > 0Fh ?
	jna .next_j1		; no -->
.get_second_digit:
	nearcall getnyb
	jc .next_j1
.have_second_digit:
	cbw			; ax = second nybble of index
	add di, di
	add di, di
	add di, di
	add di, di		; di = first digit times 16
	add di, ax		; di = full index
	lodsb
	 testopt [bx + ivFlags], ivfSeparatorSpecial
	 jnz @F
	nearcall isseparator?
	jne .next_j1
	jmp .gotseparatorspecial
@@:		; make sure no hexit follows
	nearcall getnyb
	jnc .next_j1
.gotseparatorspecial:

	neg si			; minus -> after separator
	lea cx, [bp + ?namebuffer + 1 + si]
		; -> buffered string plus 1 minus -> after separator
		; -> buffered string plus 1 minus (-> at separator plus 1)
		; -> buffered string minus -> at separator
	neg cx			; -> at separator minus -> buffered string
	mov si, word [bp + ?startpointer]
	add si, cx

.array_index_expression_common:
		; si -> behind index
	mov ax, di
	; test ah, ah
	; jnz short .error_j1	; (not possible)
	cmp al, byte [bx + ivArrayLast]
				; index > last ?
	ja short .next_j1	; then error -->

	testopt [bx + ivFlags], ivfArrayOneBased
	jz @F			; got index
	dec ax			; decrement index to get 0-based
	js short .next_j1	; if it was 0, invalid -->
@@:
	xchg ax, si		; si = index
	jmp .arraycommon	; continue


.notarray:
	nearcall isseparator?	; name ends ?
	jne short .next_j1	; no, mismatch -->

.arrayfirst:
	xor si, si		; si = array index (0 here)

.arraycommon:
	mov ch, cl		; ch = name length
	mov ax, word [bx + ivFlags]
	and ax, ivfVarLengthMask; ax = shifted encoded var length
	mov cl, ivfVarLengthShift
	shr ax, cl		; ax = encoded var length
	inc ax			; decode var length
	mov cl, al		; cl = var length

	xchg ax, si		; ax = array index

	testopt [bx + ivFlags], ivfReadOnly
	jz @F
	inc byte [bp + ?fieldtype]
				; = 1 (RO)
@@:
	xor di, di		; (NC)
	mov si, word [bx + ivSetup]
	call si			; INP:	ax = array index (0-based)
				;	di = 0
				;	cl = default size of variable (1..4)
				; CHG:	si, ax, dx
				; OUT:	NC if valid,
				;	 bx -> var, di = 0 or di -> mask
				;	 cl = size of variable (1..4)
	jc .next
	pop dx			; (discard pointer into names/headers)
	lea dx, [bx+2]		; (irrelevant to 8-/16-bit register return)
	test di, di
	jz .return_success
	jmp .return_success_di


default_variable_setup: section_of_function
	mov dl, byte [bx + ivArrayBetween]
	mov dh, 0		; dx = array between offset
	add dl, cl
	adc dh, dh		; dx = array item size
	mul dx			; dx:ax = ax times dx
	test dx, dx
	jnz short .error_j2
	add ax, word [bx + ivAddress]
				; ax -> variable
	jc short .error_j2
				; NC
	xchg ax, bx		; bx -> variable
	retn

.error_j2:
	jmp error_expr


%if _MMXSUPP
var_mm_setup: section_of_function
	cmp byte [has_mmx], 1	; MMX supported ?
	jae @F			; yes -->
	retn			; (CY)

@@:
[cpu 586]
	push ax
	lea si, [bp + ?namebuffer + 3]
				; -> may be type letter, if not index expression
	testopt [bp + ?flags], 1
	jz @F
	mov si, word [bp + ?startpointer]
	add si, word [bp + ?length_to_add]
	inc word [bp + ?length_to_add]

@@:
	lodsb			; load what may be type letter
	nearcall uppercase

	cmp al, 'Z'
	je @F			; 0 = ZX low -->
	mov bl, 2<<3		; = 10h = low
	cmp al, 'L'
	je @FF
	mov bl, 1<<3		; = 8h = SX low
	cmp al, 'S'
	je @FF
	mov bl, 3<<3		; = 18h = high
	cmp al, 'H'
	je @FF
	dec word [bp + ?length_to_add]
	dec si			; -> back at the last text
				;  (subsequent code will check for separator)
	mov al, 0
@@:
	mov bl, 0		; 0 = ZX low
@@:				; is valid letter
	push ax
	lodsb			; = candidate separator
	nearcall isseparator?	; is it ?
	pop ax
	jne .ret_CY_pop		; no -->

	test byte [bp + ?bit0_is_return_name], 1
	jz .no_return_name

	mov si, word [bp + ?return_name_pointer]
	mov byte [si + 3], al	; store the additional letter (or 0)
				; pointer + 3 ("MM" then hexit)

.no_return_name:
	test al, al
	pop ax
	jz @F
	inc ch			; increment name length
@@:
	xchg ax, bx

		; INP:	bx = register number 0..7
		;	al = 0 for ZX low, 8 for SX low, 10h for low, 18h for high
	or al, bl
	inc ax
	inc ax
	mov byte [bp + ?fieldtype], al

 %if _PM
	 sub sp, 8
	call save_scratchsel
 %if _EXPRDUALCODE && _DUALCODE
	mov dx, word [code2_seg]
 %else
	mov dx, word [code_seg]
 %endif
	nearcall setes2dx	; es => lDEBUG_CODE/2 (writable)
%else
 %if _EXPRDUALCODE && _DUALCODE
	mov es, word [code2_seg]; es => lDEBUG_CODE2
 %else
	mov es, word [code_seg]	; es => lDEBUG_CODE
 %endif
%endif
	shl bl, 3		; shift into reg field
	or bl, 07h		; code to get our ModR/M byte (r/m = [bx])
	mov byte [es:.getmmxlow_modrm], bl
				; SMC in section lDEBUG_CODE or lDEBUG_CODE2
	mov byte [es:.getmmxhigh_modrm], bl
				; SMC in section lDEBUG_CODE or lDEBUG_CODE2
	jmp @F			; try to invalidate prefetch
@@:

	push ss
	pop es

 %if _PM
	call restore_scratchsel
 %endif

	mov bx, mmxbuff
.getmmxlow:
 .getmmxlow_modrm: equ $+2	; (opcode adjusted for the right MMX reg)
	movd dword [bx], mm0	; first, initialise with low dword

	cmp al, 18h		; want to write high dword ?
	jb .mmxcommon		; no -->

.getmmxhigh:
	sub sp, byte 8
	mov ax, sp		; (ss = ds)
	xchg bx, ax		; bx -> qword on stack, ax -> mmxbuff
 .getmmxhigh_modrm: equ $+2	; (opcode adjusted for the right MMX reg)
	movq qword [bx], mm0
	xchg bx, ax		; restore bx -> mmxbuff
	pop si
	pop si			; discard low dword
	pop dword [bx]		; save high dword here

.mmxcommon:
	clc
	retn
__CPU__


.ret_CY_pop:
	pop ax
.ret_CY:
	stc
	retn
%endif


var_ri_setup: section_of_function
	lea si, [bp + ?namebuffer + 3]
	testopt [bp + ?flags], 1
	jz @F
	mov si, word [bp + ?startpointer]
	add si, word [bp + ?length_to_add]
	inc word [bp + ?length_to_add]
	push ax
	mov bl, 1		; set up 1 additional digit
	lodsb			; load what must be type letter
	jmp .expr

@@:
	push ax
	mov bl, -1
@@:
	lodsb			; al = candidate type letter, or hexit
	inc bx			; count number of hexits
	nearcall getnyb
	jnc @B			; if was a hexit, loop -->
.expr:		; al = type letter
	nearcall uppercase
	mov bh, al		; bh = letter
	cmp al, 'L'		; valid one ?
	je @F
	cmp al, 'O'
	je @F
	cmp al, 'S'
	je @F
	cmp al, 'P'
	jne .ret_CY_pop		; no -->
@@:				; is valid letter
	lodsb			; = candidate separator
	nearcall isseparator?	; is it ?
	jne .ret_CY_pop		; no -->
	pop ax

	test byte [bp + ?bit0_is_return_name], 1
	jz .interrupt_no_return_name

	mov di, bx
	and di, 255		; get number of additional hexits
	add di, word [bp + ?return_name_pointer]
	add di, 3		; amount hexits + 3 + pointer
	mov byte [di], bh	; store the additional letter

.interrupt_no_return_name:
	inc ch			; increment name length
	xchg ax, bx

		; INP:	bx = interrupt number 0..255
		;	ah = 'S' for segment, 'O' for offset, 'L' for linear
 %if _PM
	 sub sp, 8
	call save_scratchsel
 %endif
	xor si, si
%if _PM
	push bx
	nearcall setds2si	; ds => IVT
	pop bx
%else
	mov ds, si		; ds => IVT
%endif
	cmp ah, 'S'
	jne @F
	inc si
	inc si			; si = 2 (displacement to get segment)
@@:
	add bx, bx
	add bx, bx		; ds:bx -> IVT entry
	push word [bx + si + 2]
	push word [bx + si]	; get segment:offset or trash:segment
	 push ss
	 pop ds
	mov bx, interrupt_var
	pop word [bx]
	pop word [bx + 2]
				; store in variable
 %if _PM
	call restore_scratchsel
 %endif
	cmp ah, 'P'		; for pointer type ?
	je .var_size_4		; yes -->
	cmp ah, 'L'
	jne @FF
	 push cx
	xor ax, ax
	xchg ax, word [bx + 2]	; clear high word, get segment
	xor dx, dx		; dx:ax = segment
	mov cx, 4
@@:
	shl ax, 1
	rcl dx, 1
	loop @B			; shift up 4 bits
	add word [bx], ax
	adc word [bx + 2], dx	; add to dword
	 pop cx
	mov cl, 3		; 3byte variable (21 bits needed)
	db __TEST_IMM16		; (skip mov)
.var_size_4:
	mov cl, 4		; dword variable
@@:
	xor di, di		; (NC)
	retn

.ret_CY_pop:
	pop ax
.ret_CY:
	stc
	retn



%if _INPUT_FILE_BOOT || _INPUT_FILE_HANDLES
var_ysf_setup: section_of_function
	xchg ax, bx			; bx = index
	mov al, byte [bp + ?namebuffer + 1]
	cmp al, 'S'
	jne @F
%if _INPUT_FILE_BOOT
	testopt [internalflags2], dif2_input_file_boot
	mov al, 'B'
	jnz @F
%endif
	mov al, 'H'
@@:
	cmp al, 'B'
	jne @F
%if _INPUT_FILE_BOOT
	testopt [internalflags2], dif2_input_file_boot
	jnz @FF
%endif
.notysf:
	stc
	retn
@@:
%if _INPUT_FILE_HANDLES
	cmp al, 'H'
	jne short .notysf
	testopt [internalflags2], dif2_input_file
	jz .notysf
%else
	jmp .notysf
%endif
@@:
				; bx = index
%if _INPUT_FILE_HANDLES
	mov di, input_file_handles.active
				; -> active index for YHF
%endif
%if _INPUT_FILE_BOOT
 %if _INPUT_FILE_HANDLES
	cmp al, 'B'
	jne @F
 %endif
	mov di, load_input_file.active
				; -> active index for YBF
@@:
%endif
	neg bx			; -1 .. 0
	jz @F			; if 0, always valid -->
		; index was nonzero, meaning bx now holds a
		;  negative number. if [.active] is large enough,
		;  adding it to bx yields a positive number, which
		;  carries in the addition. check for that.
	add bx, word [di]
	jnc .notysf		; not valid index -->
	jmp @FF			; valid, use it
@@:
	add bx, word [di]
				; 0 means the topmost level
@@:
%if _INPUT_FILE_BOOT
 %if _INPUT_FILE_HANDLES
	cmp al, 'B'
	jne @F
 %endif

	mov ax, LOAD_INPUT_FILE_SIZE
	xchg ax, bx
	mul bx			; dx ignored, should be zero
	xchg ax, bx		; bx = offset into array
	add bx, load_input_file - LOADDATA3 + ldFATType
				; -> flag word
	jmp @FF

@@:
%endif
%if _INPUT_FILE_HANDLES
	shl bx, 1
	shl bx, 1
	shl bx, 1		; to qword array index
 %if INPUTFILEHANDLE_size != 8
  %error Unexpected structure size
 %endif
	add bx, input_file_handles + ifhFlags
				; -> flags word
%endif
@@:
	mov di, mask_ysf	; give the mask of read-only bits
	clc
	retn
%endif


	lleave ctx


		; INP:	al, si-> string
		; OUT:	CY if no valid digit 0..7
		;	NC if valid digit,
		;	 bl = 0..7
		;	 al, si-> behind digit specification
		; CHG:	bl
getstmmxdigit: section_of_function
	cmp al, '('
	je .paropen
	call .isdigit?
	jc .ret
.retlodsb:
	lodsb
.ret:
	retn

.paropen:
	push ax
	push si
	lodsb
	call .isdigit?
	jc .retpop
	lodsb
	cmp al, ')'		; closing parenthesis ?
	stc
	jne .retpop		; no --> (CY)
	add sp, byte 4		; discard saved registers (NC)
	jmp short .retlodsb

.retpop:
	pop si
	pop ax
	retn

.isdigit?:
	mov bl, al
	sub bl, '0'
	cmp bl, 8		; valid digit 0..7 ? (CY if so)
	cmc			; NC if so
	retn


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif
isvariable_code_size equ $ - isvariable_code


	usesection lDEBUG_DATA_ENTRY
isvariable_data_size equ $ - isvariable_data

	numdef SHOW_ISVARIABLE_SIZE, _DEFAULTSHOWSIZE
%if _SHOW_ISVARIABLE_SIZE
 %assign CODESIZE isvariable_code_size
 %assign DATASIZE isvariable_data_size
 %warning isvariable size: CODESIZE code, DATASIZE data
%endif


	align 2, db 0
separators:	countedw 32,9,13,"/\,L;]:)(=.'",'"',0

%ifn _EXPRESSIONS
 %error Building without the expression evaluator is not possible right now
%endif

%if _EXPRESSIONS

	align 4, db 0
hhvar:	dd 0	; left-hand operand for operator functions
%if _HHDIVREMAIN
hh_div_dividend:
	dd 0
hh_div_divisor:
	dd 0
%endif
	align 2, db 0
hh_depth:
	dw 0
hh_depth_of_single_term:
	dw 0
hhflag:	db 0	; &2: getdword called from hh, default to sub (precedence over 1)
		; &1: getdword called from hh, default to add
		; &4: getdword defaulted to add/sub for hh
		; (Note that during recursive getexpression calls (ie,
		;  bracket handling), hh_depth is incremented to 2 and
		;  higher. As these flags are only used with hh_depth == 1,
		;  this means inside brackets the hh defaulting is
		;  not in effect.)
hhtype:	db 0	; type info on left-hand operand for operator functions
%if _HHDIVREMAIN
hh_div_active:
	db 0
%endif
%endif


%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif

		; Get a numerical value from input line
		; INP:	al = first character
		;	si-> next character
		; OUT:	bx:dx = numerical value
		;	ah&80h = whether a pointer
		;	ah&40h = whether a signed type
		;	ah&3Fh = number of significant bits
		;	 one-based position of highest one bit if unsigned or signed but positive
		;	 one-based position of lowest one bit from the top if negative signed
		;
		; Note:	This invalidates the symbol table access slice.
getdword: section_of_function
getexpression: section_of_function
	inc word [hh_depth]
	nearcall skipcomm0
	push cx
	push di
	lframe
	lenter

	xor cx, cx
	push cx
	lvar word, ??Count
%define lCount ???Count

		; The first number field's operator is initialized to
		; the dummy right-operand operator, which is set up with
		; the highest precedence. This means it'll be processed
		; immediately in the first iteration below.
	mov cl, OPERATOR_RIGHTOP
	push cx				; initialize dummy first number operator
	sub sp, byte 6+4
	lvar 6, ??A
%define lA ???A
	lvar 6, ??B
%define lB ???B

.loop:
		; Get next term of an expression. A term is one variable,
		; one immediate number, one expression contained within
		; round brackets or one expression used to access memory.
		; This code also parses any number of unary operators
		; (including type conversions) in front of the term.
		;
		; INP:	(si-1)-> first character
		; OUT:	(see label .term_done)
		;	bx:dx = numerical value of term
		;	ah&80h = whether a pointer
		;	ah&40h = whether a signed type
		;	al = first character behind term
		;	si-> line
		; CHG:	di, cx
		;
		; This part might be simplified by directly modifying 6byte[bp+lB]
		; instead of setting bx:dx and ah. (The current interface is one of
		; the holdovers of getexpressionterm as a separate function.) In
		; that case, bx:dx and ah presumably will then be simply added to
		; the CHG specification.
.term:
	dec si
	push si			; -> term

		; count unary operators and type specifiers,
		;  get the bit mask of required bytes
		;  and skip past the operators and specifiers
	nearcall count_unary_operators

%if _INDIRECTION
	cmp al, '['
	je .indirection		; handle indirected value -->
%endif
	cmp al, '('
	je .parens		; handle term with precedence -->

	push dx			; remember the count+1
				; (only if no indirection or bracket)

	dec si
	mov dx, msg.value	; does a VALUE keyword for VALUE x IN y go here ?
	nearcall isstring?
	je .value_in		; yes -->

	mov dx, msg.linear	; does a LINEAR keyword go here ?
	nearcall isstring?
	je .linear		; yes -->

%if _PM
	mov dx, msg.desctype
	nearcall isstring?
	je .desctype		; yes -->
%endif

	lodsb

	nearcall isvariable?	; is it a variable ?
	jc .check_literal_or_symbol
				; no, may be an immediate value or symbol -->

.variable:
	xchg bx, dx		; bx-> high word
	mov ah, cl
	xor cx, cx
	cmp ah, 2
	jbe .variable_nohigh
	mov cx, word [bx]	; get high word
	cmp ah, 3
	ja @F
	mov ch, 0
@@:
.variable_nohigh:
	xchg bx, dx		; bx-> low word
	mov dx, word [bx]	; get low word
	mov bx, cx		; high word/zero
	cmp ah, 1
	ja .variable_notbyte
	mov dh, 0		; limit to byte
.variable_notbyte:
.symbol:
	mov ah, 0		; type pointer=0 signed=0
	jmp .term_end


.check_literal_or_symbol:
%if _SYMBOLIC
	nearcall issymbol?	; is it a symbol ?
	jnc .symbol		; yes -->
				; no, must be an immediate value
%endif

.literal:
	xor bx, bx		; (in case of decimal base shortcut:
	mov dx, 10		;   set base: decimal)
	cmp al, '#'		; shortcut change to decimal base, or string literal?
	jne .lithex_common
	cmp byte [si], '"'
	je .lit_string
	cmp byte [si], "'"
	je .lit_string
	jmp .lit_base		; decimal base -->

.lit_string:
	xor dx, dx
	lodsb
	xchg al, ah

	call @F
	mov dl, al
	call @F
	mov dh, al
	call @F
	mov bl, al
	call @F
	mov bh, al
	lodsb
	cmp al, ah
	je @FFF
.err3:
	jmp .err2

@@:
	lodsb
	cmp al, ah
	je @F
	nearcall iseol?.notsemicolon
	je .err3
.lit_string_retn:
	retn

@@:
	lodsb
	cmp al, ah
	je .lit_string_retn
	pop cx			; (discard near return address)
	dec si
@@:
	lodsb
	jmp .lit_end_string


.lithex_common:
	call .lit_ishexdigit?	; the first character must be a digit then
	jc .err2
	xor dl, dl		; initialize value
.lithex_loopdigit:
	cmp al, '_'
	je .lithex_skip
	call .lit_ishexdigit?	; was last character ?
	jc .lit_end		; yes -->
	test bh, 0F0h		; would shift bits out ?
	jnz .err2
	nearcall uppercase
	sub al, '0'
	cmp al, 9		; was decimal digit ?
	jbe .lithex_decimaldigit; yes -->
	sub al, 'A'-('9'+1)	; else adjust for hexadecimal digit
.lithex_decimaldigit:
	mov cx, 4
.lithex_loopshift:
	shl dx, 1
	rcl bx, 1
	loop .lithex_loopshift	; *16
	or dl, al		; add in the new digit
.lithex_skip:
	lodsb
	jmp short .lithex_loopdigit

.lit_end:
	cmp al, '#'		; base change specification?
	je .lit_base		; yes -->
.lit_end_string:
	nearcall isseparator?	; after the number, there must be a separator
	jne .err2		; none here -->
	mov ah, 0		; type pointer=0 signed=0
	jmp .term_end		; okay -->
.lit_base:
	test bx, bx		; insure base <= 36
	jnz .err2
	cmp dx, byte 36
	ja .err2
	cmp dx, byte 2		;  and >= 2
	jb .err2		; otherwise error -->

	lodsb
	cmp dl, 16		; hexadecimal ?
	je .lithex_common	; yes, use specific handling -->

	mov di, dx		; di = base
	mov cl, dl
	add cl, '0'-1
	cmp cl, '9'
	jbe .lit_basebelow11
	mov cl, '9'
.lit_basebelow11:		; cl = highest decimal digit for base ('1'..'9')
	mov ch, dl
	add ch, 'A'-10-1	; ch = highest letter for base ('A'-x..'Z')

	call .lit_isdigit?	; first character must be a digit
	jc .err2
	xor dx, dx		; initialize value
.lit_loopdigit:
	cmp al, '_'
	je .lit_skip
	call .lit_isdigit?	; was last character ?
	jc .lit_end		; yes -->
	nearcall uppercase
	sub al, '0'
	cmp al, 9		; was decimal digit ?
	jbe .lit_decimaldigit	; yes -->
	sub al, 'A'-('9'+1)	; else adjust for hexadecimal digit
.lit_decimaldigit:
	push ax
	mov ax, dx
	push bx
	mul di			; multiply low word with base
	mov bx, dx
	mov dx, ax
	pop ax
	push dx
	mul di			; multiply high word with base
	test dx, dx
	pop dx
	jnz .err2		; overflow -->
	add bx, ax		; add them
	pop ax
	jc .err2		; overflow -->
	add dl, al		; add in the new digit
	adc dh, 0
	adc bx, byte 0
	jc .err2		; overflow -->
; The value 3#102002022201221111210 is exactly equal to ffffFFFFh
;  so 3#102002022201221111211 (or ...2) overflows 32 bits and
;  needs to be detected here when adding in the as yet least
;  significant digit. This is why this jump is needed.
; This also applies to decimal #4294967296.

.lit_skip:
	lodsb
	jmp short .lit_loopdigit

.err2:
	jmp error_expr


%if _PM
.desctype:
	pop ax			; discard unary operators counter

%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_DESCTYPE_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_DESCTYPE_STACK_CHECK
	call stack_check	; abort if deep recursion
%endif
	test ax, msg.stack_overflow.desctype

	lodsb
	nearcall ispm
	jnz short .err2
	nearcall skipwh0
	nearcall getword
_386	push edx
_386	pop dx
_386	lar edx, edx
_386	jz @F
_386	xor edx, edx
@@:
_386	shr edx, 8
_386	push dx
_386	pop edx
subcpu 286
_no386	lar dx, dx
_no386	jz @F
_no386	xor dx, dx
@@:
_no386	xchg dl, dh
subcpureset
	xor bx, bx
	nearcall skipcomm0
	mov ah, 0		; type pointer=0 signed=0
	jmp .term_end_recount
%endif


.linear:
	pop ax			; discard unary operators counter

%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_LINEAR_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_LINEAR_STACK_CHECK
	call stack_check	; abort if deep recursion
%endif
	test ax, msg.stack_overflow.linear

	nearcall skipwhite
_386_PM	push word [bAddr32]
_386_PM	push edx
_386_PM	pop dx
	mov bx, word [reg_ds]
	nearcall getlinearaddr
_386_PM	push dx
_386_PM	pop edx
_386_PM	pop word [bAddr32]
	jc .err2

%if !_LOADER
	_386_PM_o32	; or dword
	or word [bp_offset], strict byte -1
				; do not use a preferred offset
%endif

	nearcall skipcomm0
	mov ah, 0		; type pointer=0 signed=0
	jmp .term_end_recount


.value_in:
	pop ax			; discard unary operators counter

%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_VALUE_IN_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_VALUE_IN_STACK_CHECK
	call stack_check	; abort if deep recursion
%endif
	test ax, msg.stack_overflow.value_in

	nearcall skipcomma
	dec si
	mov dx, msg.executing
	nearcall isstring?
	jne @F
	nearcall skipcomma
	push si
	mov si, msg.executing_value_range
	nearcall get_value_range
	pushf			; ! preserve CF
	nearcall chkeol
	popf
	pop si
	dec si			; preserves CF
	lodsb
	jmp @FF

@@:
	nearcall get_value_range; OUT:	cx:di = from, bx:dx = to
@@:
	nearcall skipcomm0

		; If the VALUE is a not normal range (zero length
		;  or from > to) then we still have to parse the
		;  remaining input line to find the end of the
		;  VALUE x IN y construct. So we do this with a
		;  sort of hack: we take the invalid / unset
		;  range values, do all the (invalid) comparisons,
		;  but do not count any of the possible matches.
		; Look at .value_in.found for this hack.
	lframe none, nested
	lenter
	lvar word, bit0_do_not_match
	 pushf
	lvar dword, value_from
	 push cx
	 push di
	lvar dword, value_to
	 push bx
	 push dx
	xor cx, cx
	lvar dword, found
	 push cx
	 push cx

	push cx			; zero terminator of keyword remembered offsets
	dec si
	mov dx, msg.in
	nearcall isstring?
.err3_NZ:
.err4_NZ:
	jne .err2

	nearcall skipcomma
	dec si

.value_in.loop:
	nearcall skipwhite
	dec si
	mov bx, msgtable_value_range
				; -> table

@@:
	mov dx, word [bx]
	test dx, dx
	jz @F			; last checked, go get range -->
	nearcall isstring?
	je .value_in.keyword	; found a keyword -->
	add bx, 4		; go to next table entry
	jmp @B

.value_in.keyword:
%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_VALUE_IN_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_VALUE_IN_STACK_CHECK
	call stack_check	; abort if deep recursion
%endif
	test ax, msg.stack_overflow.value_in

	nearcall skipwhite	; skip past blanks
	push si			; remember offset after keyword
	mov si, word [bx + 2]	; -> keyword replacement text
	jmp .value_in.loop	; handle replacement -->

@@:
	nearcall get_value_range; OUT:	cx:di = from, bx:dx = to
	jc .value_in.next

		; ! (StartVal > EndMatch)
	cmp word [bp + ?value_from + 2], bx
	jne @F
	cmp word [bp + ?value_from], dx
@@:
	ja .value_in.next

		; ! (EndVal < StartMatch)
	cmp word [bp + ?value_to + 2], cx
	jne @F
	cmp word [bp + ?value_to], di
@@:
	jb .value_in.next

.value_in.found:
	mov cx, word [bp + ?bit0_do_not_match]
	not cx
	and cx, 1
	add word [bp + ?found], cx
	adc word [bp + ?found], 0

.value_in.next:
@@:
	nearcall skipwh0
	cmp al, ','
	je .value_in.loop

	pop cx			; get next remembered offset
	jcxz @F			; if none -->
	mov si, cx		; -> behind keyword
	dec si
	lodsb			; reload al
	jmp @B			; continue after the keyword -->

@@:
	pop dx
	pop bx			; pop ?found counter variable
	lleave

	mov ah, 0		; type pointer=0 signed=0
	jmp .term_end_recount


%if _INDIRECTION
.indirection:
	call stack_check_indirection
				; abort if deep recursion
	test ax, msg.stack_overflow.indirection

_386_PM	push word [bAddr32]
_386_PM	push edx
_386_PM	pop dx

	nearcall skipcomma	; also skips the '[' in al
	mov bx, word [reg_ds]	; default segment/selector
	push cx			; save previous bit mask (ch)
	nearcall getaddrX	; (recursively calls getexpression:)
	pop cx
	mov cl, 0
	cmp al, ']'		; verify this is the closing bracket
.err5_NZ:
	jne .err4_NZ		;  if not -->
	lodsb			; get next character
		; bx:(e)dx-> data
		; ch = bit mask of required bytes
		; cl = 0

..@hh_indirection_memory_access_start:
	push ax
	push bp
	xor bp, bp
	push bp
	push bp
	mov bp, sp		; -> buffer

	nearcall prephack
	nearcall dohack
			; Regarding how this loop handles cx,
			;  remember that ch holds the flags for
			;  the required bytes.
			;  And cl is initialised to 0. In
			;  each iteration, cl is incremented.
			;  The loop instruction then decrements
			;  cl again, but the entire cx is only
			;  zero if no more bytes are required.
			; (This hack saves a single byte over
			;  the "test ch, ch \ jnz" alternative
			;  but it isn't very pretty. It
			;  also probably slows down a bit.)
.indirection_loop:
	shr ch, 1		; need to read this byte ?
	jnc .indirection_skip	; no -->
%if _DUALCODE && _EXPRDUALCODE
	usesection lDEBUG_CODE
..@hh_code1_indirection_memory_access_start:

section_of ..@hh_readmem
	dualfunction
..@hh_readmem:
	lframe dualdistance, nested
	lframe_needonlyregistered
	lenter
	call readmem
	lleave
	dualreturn
	lret

..@hh_code1_indirection_memory_access_end:
	usesection lDEBUG_CODE2
	dualcall ..@hh_readmem	; else read byte
%else
	nearcall readmem	; else read byte
%endif
	mov byte [bp+0], al	; store byte
.indirection_skip:
	inc cx			; = 1 if no more to read
	inc bp			; increase buffer pointer
	_386_PM_o32		; inc edx
	inc dx			; increase offset
	loop .indirection_loop	; read next byte if any -->
	nearcall unhack

	pop dx
	pop bx
	pop bp
	pop ax
..@hh_indirection_memory_access_end:

_386_PM	push dx
_386_PM	pop edx
_386_PM	pop word [bAddr32]
	mov ah, 0		; type pointer=0 signed=0
	jmp short .term_end_recount
%endif


.parens:
%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_PARENS_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_PARENS_STACK_CHECK
	call stack_check	; abort if deep recursion
%endif
	test ax, msg.stack_overflow.parens

	lodsb
	nearcall getexpression	; (recursive) bx:dx = number, ah = type
	cmp al, ')'		; closing parens ?
	jne .err5_NZ		; no -->
	lodsb

.term_end_recount:
	db __TEST_IMM16		; skip pop, stc; NC
.term_end:
	pop cx			; get count+1 of unary operators and type specifiers
	stc
	pop di			; -> term
	xchg si, di
	push di			; save -> behind
		; si-> unary operators and types
	jc .unary_processnext	; if we preserved the count -->

	push bx
	push dx
	push si
	nearcall count_unary_operators
	mov cx, dx		; get count+1 again
	pop si
	pop dx
	pop bx

.unary_processnext:
	loop .unary_doprocess
	pop si			; -> behind term
	 dec si			; -> character to reload in skipwhite
	jmp short .term_done

.unary_doprocess:
	push si
	push cx
	push bx
	push dx

	mov di, cx		; count+1 of operators to skip
	nearcall count_unary_operators_restrict	; skip them
	jnz .err		; if not enough --> (?!)

	nearcall istype?	; get type info if it's a type
	jc .unary_processnotype	; isn't a type -->
	xchg al, ah		; al = type input
	shr bx, 1		; CF = signedness
	lahf			; with CF = signedness
	mov cx, word [ typehandlers + bx ]	; function
.unary_processcall:
	pop dx
	pop bx
%if _HHDIVREMAIN
	mov byte [hh_div_active], 0
%endif
	call cx			; call type or unary operator handler
				;  bx:dx = output number, ah = type
	pop cx			; restore processing counter
	pop si			; restore ->term
	jmp short .unary_processnext

.unary_processnotype:
	nearcall isunaryoperator?; get unary operator index
	jne .err		; if no unary operator --> (?!)
	mov bx, cx
	shl bx, 1
	mov cx, word [ unaryoperatorhandlers + bx ]
	jmp short .unary_processcall

.err:
	jmp error_expr

.term_done:	; bx:dx = number, ah = type
		; get the operator following this number
	nearcall skipwhite
	mov word [bp+lB+0], dx
	mov word [bp+lB+2], bx		; store numeric value
	mov bx, word [hh_depth_of_single_term]
	cmp bx, word [hh_depth]
	je .operator_invalid
	nearcall isoperator?		; cl = operator index (if any)
	je .operator_apparently_valid
	nearcall iseol?			; end of line follows ?
	je .operator_invalid
.hh_twofold_check:
		; If we are processing a ?? :: conditional
		;  construct then we want the :: to end
		;  the parsing of the expression at that
		;  point, to then execute the OPERATOR_COND
		;  function of_cond. That means here we
		;  want to not apply the hh twofold case.

		; The next check is for an ?? :: construct
		;  if the :: happens at hh depth == 1 and
		;  there is no nested ?? :: construct.
	cmp byte [bp + lA + 4], OPERATOR_COND
	je .operator_invalid		; special check for conditional operator

		; The next check is for a nested ?? ::
		;  construct, or if the clause between
		;  ?? and :: contains any other operators
		;  than ??. (All operators have higher
		;  precedence than ??.)
		; If any ?? is waiting for its :: then we
		;  must accept the next :: as an invalid
		;  operator, thus not as a separator that
		;  activates the hh twofold operation.
	mov cx, word [bp + lCount]	; = how many operators waiting
	jcxz @FF			; if none waiting --> (do hh twofold)
	mov bx, sp			; -> first waiting operator on stack
@@:
	cmp byte [bx], OPERATOR_COND	; is it a conditional operator ?
	je .operator_invalid		; yes, treat as invalid operator -->
					;  (it must be a :: to be valid)
	add bx, 6			; -> next waiting operator on stack
	loop @B				; loop if any waiting operator left -->
@@:
	cmp word [hh_depth], 1		; are we in first level expression ?
	jne .operator_invalid		; no, do not do special H operation -->
	test byte [hhflag], 1|2		; special H operation requested ?
	jz .operator_invalid		; no -->
	mov bl, OPERATOR_MINUS		; assume it's sub
	test byte [hhflag], 2		; sub requested by H ?
	jnz .hh_twofold_found		; yes -->
	dec bx				; else it must be add
%if (OPERATOR_MINUS - 1) != OPERATOR_PLUS
 %error Remove optimisation
%endif
.hh_twofold_found:
	or byte [hhflag], 4		; set flag for H twofold operation
	dec si
	nearcall skipcomma
	jmp short .operator_done	; return this -->


		; INP:	bx = offset into operator dispatchers
		;	 = 2 * OUT:cx of isoperator? if ZR
		;	al = candidate operator text start
		;	si -> subsequent text
		; OUT:	bl = operator index, 0 if no valid operator
		;	 if valid: si -> after operator
		;	 if invalid: si unchanged (?)
		; CHG:	al, bh, dx
..@call_operator_dispatchers: section_of_function
	push word [operatordispatchers + bx]
					; on stack: -> operator dispatcher
	mov dx, word [operator_default_strings + bx]
					; dx = default operator string
	mov bx, word [operator_default_indexes + bx]
					; bx = default operator index
	retn

.operator_apparently_valid:
	mov bx, cx
	add bx, bx
	call ..@call_operator_dispatchers
	test bl, bl			; valid ?
	jz .hh_twofold_check		; no, check for H twofold operation -->
	nearcall skipwhite
	db __TEST_IMM16
.operator_invalid:
	xor bx, bx			; bl = 0 (no operator)
.operator_done:
	mov bh, ah			; bh = type info
	mov word [bp+lB+4], bx		; store type and following operator

%if _EXPRESSION_DEBUG
	push ax
	push bx
	push cx
	push dx
	push si
	push di

	lea bx, [bp + lA]
	mov dx, .msgA
	call .debugdump

	lea bx, [bp + lB]
	mov dx, .msgB
	call .debugdump

	pop di
	pop si
	push si
	push di
	call .debugline
	jmp .debugend

.debugline:
	call .debugpad
	mov dx, .msg_end1
	nearcall putsz

	dec si
	mov dx, si
@@:
	lodsb
	nearcall iseol?
	jne @B
	mov cx, si
	dec cx
	sub cx, dx
	nearcall puts

	mov dx, .msg_end2
	nearcall putsz
	retn

	usesection lDEBUG_DATA_ENTRY

.msgB:		asciz "B = "
.msgA:		asciz "A = "
.msg@:		asciz "@ = "
.msgPop:	asciz "Pop = "
.msg_opA:	asciz "(A) "
.msg_opB:	asciz " op (B)",13,10
.msg_none:	asciz "none"
.msg_op:	asciz ", "
.msg_end:	db " op"
.msg_linebreak:	asciz 13,10
.msg_end1:	asciz '"'
.msg_end2:	asciz '"',13,10,13,10
.msg_looping_cond:
		asciz "Looping for OPPREC_COND",13,10

%if _EXPRDUALCODE && _DUALCODE
	usesection lDEBUG_CODE2
%else
	usesection lDEBUG_CODE
%endif

.debugpad:
	mov cx, word [hh_depth]
	dec cx
	jz @FF
@@:
	mov al, 32
	nearcall putc
	loop @B
@@:
	retn

.debugdump:
	call .debugpad
	nearcall putsz

	cmp byte [bx + 4], OPERATOR_RIGHTOP
	jne @F
	mov dx, .msg_none
	nearcall putsz
	jmp .debugdump_none

@@:
	sub sp, 8
	mov di, sp
	mov ax, word [bx + 2]
	nearcall hexword
	mov ax, word [bx]
	nearcall hexword
	mov cx, di
	mov di, sp
@@:
	cmp byte [di], '0'
	jne @F
	inc di
	cmp cx, di
	ja @B
	dec di
@@:
	sub cx, di
	mov dx, di
	push bx
	nearcall puts
	pop bx
	add sp, 8

.debugdump_none:
	mov dx, .msg_op
	nearcall putsz
	mov si, word [bx + 4]
	and si, 00FFh
	add si, si
	mov dx, word [operatornames + si]
	nearcall putsz
	mov dx, .msg_end
	nearcall putsz
	retn

.debugend:
	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
%endif

	mov cl, byte [bp+lA+4]
	call .compare_operators		; (cmp bl, cl = cmp Boprtr, Aoprtr)
	jb .high_precedence_A		; compute the first operand first -->
					;  (jump taken for invalid Boprtr too)
	jne @F
		; If we get the same operator precedence for
		;  Boprtr and Aoprtr we generally want to
		;  handle this as a high-precedence A.
		; This is not true for operator cond. If
		;  an expression like this is parsed:
		;  Acond ?? Bcond ?? Btrue :: Bfalse :: Afalse
		; Then we get first:
		;  A = none, right op
		;  B = Acond, cond op
		; After the right op is processed:
		;  A = Acond, cond op
		;  B = Bcond, cond op
		; Next we want:
		;  @ = Acond, cond op
		;  A = Bcond, cond op
		;  B = Btrue, invalid op (::)
		; If we did high precedence A here instead
		;  we would get:
		;  A = (Acond) cond op (Bcond)
		; At this point of_cond would parse "Btrue..."
		;  as the :: clause, failing because it doesn't
		;  start with a "::".
	cmp bx, OPPREC_COND
	jne .high_precedence_A

@@:
	inc word [bp+lCount]		; increase loop count
	push word [bp+lA+0]
	push word [bp+lA+2]
	push word [bp+lA+4]		; push A and its operator

%if _EXPRESSION_DEBUG
	push ax
	push bx
	push cx
	push dx
	push si
	push di

	lea bx, [bp + lA]
	mov dx, .msg@
	call .debugdump
	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
%endif

	push word [bp+lB+4]
	push word [bp+lB+2]
	push word [bp+lB+0]
	pop word [bp+lA+0]
	pop word [bp+lA+2]
	pop word [bp+lA+4]		; set A to B, including operator

%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_PRECEDENCE_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_PRECEDENCE_STACK_CHECK
	call stack_check		; abort if deep recursion
%endif
	test ax, msg.stack_overflow.precedence

d4	call expr_d4message
d4	asciz "getexpression: Entering loop/recursion",13,10

.loop_j:
	jmp .loop			; start again (former B as first term) -->

.cont:

d4	call expr_d4message
d4	asciz "getexpression: End of loop/recursion",13,10

	push word [bp+lA+4]
	push word [bp+lA+2]
	push word [bp+lA+0]
	pop word [bp+lB+0]
	pop word [bp+lB+2]
	pop word [bp+lB+4]		; set B to A, including operator

	pop word [bp+lA+4]
	pop word [bp+lA+2]
	pop word [bp+lA+0]		; pop A and its operator

%if _EXPRESSION_DEBUG
	push ax
	push bx
	push cx
	push dx
	push si
	push di

	lea bx, [bp + lA]
	mov dx, .msgPop
	call .debugdump
	lea bx, [bp + lB]
	mov dx, .msgB
	call .debugdump

	pop di
	pop si
	push si
	push di
	call .debugline

	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
%endif

.high_precedence_A:

%if _EXPRESSION_DEBUG
	push ax
	push bx
	push cx
	push dx
	push si
	push di

	call .debugpad
	mov dx, .msg_opA
	nearcall putsz
	lea bx, [bp + lA]
	mov si, word [bx + 4]
	and si, 00FFh
	add si, si
	mov dx, word [operatornames + si]
	nearcall putsz
	mov dx, .msg_opB
	nearcall putsz

	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
%endif

	mov cx, word [bp+lA+4]		; retrieve A's type info and operator
	push word [bp+lA+2]
	push word [bp+lA+0]
	 mov ax, word [bp+lB+4]		;  retrieve B's type info and operator
	  mov byte [hhtype], ch		;   set type info
	pop word [hhvar]
	  mov ch, 0			;   cx = A's 1-based operator index
	pop word [hhvar+2]		; retrieve A's number
	 mov dx, word [bp+lB+0]
	  mov di, cx
	 mov bx, word [bp+lB+2]		;  retrieve B's number
	  add di, di			;   = offset into dispatch table
	 push ax			;  preserve B's operator
%if _HHDIVREMAIN
	mov byte [hh_div_active], 0
%endif
	call near [operatorfunctions+di]; compute: (A) operatorA (B)
	 pop cx				; cl = B's operator

	mov word [bp+lA+0], dx
	mov al, cl			; B's operator
	mov word [bp+lA+2], bx
	mov word [bp+lA+4], ax		; store result in A, with B's operator

%if OPERATOR_INVALID != 0
 %error Remove optimisation
%endif
	test al, al			; (previous B's) operator valid ?
	jz .end				; no, end of sequence -->

	cmp word [bp+lCount], byte 0	; in recursion ?
	je .loop_j			; no, loop -->

	pop bx
	push bx				; retrieve saved ('@') operator

	call .compare_operators		; (cmp bl, cl = cmp @oprtr, Aoprtr)
	jb .loop_j			; A's operator's precedence higher -->

%if _EXPRESSION_DEBUG
	push ax
	push bx
	push cx
	push dx
	push si
	push di

	cmp bx, OPPREC_COND
	jne @F
	mov dx, .msg_looping_cond
	nearcall putsz
@@:

	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
%endif

	cmp bx, OPPREC_COND
	je .loop_j

d4	call expr_d4message
d4	asciz "getexpression: Loop/recursion found to be not necessary anymore",13,10

;	dec word [bp+lCount]
;	jmp .cont			; return to previous level -->

.end:
	dec word [bp+lCount]		; decrease loop count
	jns .cont			; process next operand from stack -->

	mov dx, word [bp+lA+0]
	mov bx, word [bp+lA+2]		; retrieve A
	mov ax, word [bp+lA+4]		;  (discard (invalid) operator)

	lleave code			; remove the stack frame

	pop di
	pop cx				; restore registers

	dec word [hh_depth]
	 dec si
	 lodsb				; (restore al)

.countsignificantbits: section_of_function getexpression
	push cx
	push dx
	push bx
	mov cx, 1
	 push bx
	test ah, 40h			; signed type ?
	jz .unsigned			; no -->
	test bh, 80h			; negative value ?
	jz .unsigned			; no -->
.signed:
	and bx, dx
	inc bx				; = 0 if -1 (all bits set)
	 pop bx
	jz .done			; is -1, 1 significant bit -->
	mov cl, 32+1+1			; number of significant bits is 1 + 1-based index of highest clear bit
.signedloop:
	shl dx, 1
	rcl bx, 1			; shift up the number
	dec cx				; maintain index
	jc .signedloop			; still a set bit -->
	jmp short .done
.unsigned:
	or bx, dx			; = 0 if 0 (all bits cleared)
	 pop bx
	jz .done			; is 0, 1 significant bit -->
	mov cl, 32+1			; number of significant bits is 1-based index of highest set bit
.unsignedloop:
	shl dx, 1
	rcl bx, 1
	dec cx
	jnc .unsignedloop
	test ah, 40h			; positive signed value ?
	jz .done			; no -->
	inc cx				; then the following zero bit is required too
.done:
	and ah, 1100_0000b
		; insure we only pass the top two type bits
	or ah, cl
		; low 6 bits = number of significant bits
	pop bx
	pop dx
	pop cx
	retn


		; INP:	bl = operator index 1
		;	cl = operator index 2
		; OUT:	flags as for "cmp precedence1, precedence2"
		; CHG:	bx, cx
.compare_operators:
	call .getprecedence
	call .getprecedence
	cmp bx, cx
	retn

.getprecedence:
	xor bh, bh
	mov bl, byte [operatorprecedences+bx]
	xchg bx, cx
	retn

getexpression.lit_ishexdigit?: section_of_function
	mov cx, "9F"
getexpression.lit_isdigit?: section_of_function
	cmp al, '0'
	jb .no
	cmp al, cl
	jbe .yes
	push ax
	nearcall uppercase
	cmp al, ch
	ja .no_p
	cmp al, 'A'
	jb .no_p
	pop ax
.yes:
	clc
	retn

.no_p:
	pop ax
.no:
	stc
	retn


of_cond: section_of_function
	push ax			; second operand type
	dec si
	lodsw
	cmp ax, "::"
	jne short .error
%if _EXPRESSION_INDIRECTION_STACK_CHECK == _EXPRESSION_COND_STACK_CHECK
	call stack_check_indirection
%else
	mov ax, _EXPRESSION_COND_STACK_CHECK
	call stack_check	; abort if deep recursion
%endif
	test ax, msg.stack_overflow.cond

	mov ax, word [hhvar]
	or ax, word [hhvar + 2]	; ax = flag (zero if to take third operand)
	push bx
	push dx
	push ax			; preserve stack
	lodsb
	nearcall getexpression	; parse third operand
	nearcall skipcomm0	; allow comma afterwards
	pop cx
	jcxz @F			; if to take third operand -->
	pop dx
	pop bx			; take second operand
	mov cl, al
	pop ax			; ah = second operand type
	mov al, cl		; preserve al
	jmp @FF

@@:
	pop cx
	pop cx			; discard second operand value
	pop cx			; discard second operand type
@@:
	pop cx			; discard near return address
	pop cx			; discard cx on stack

	mov byte [bp+lA+4], OPERATOR_RIGHTOP
	dec si
	jmp getexpression.term_done

.error:
	jmp error_expr

	lleave ctx


		; INP:	si-> possible unary operators
		; OUT:	dx = 1 + count of unary operators
		;	al, si-> behind identified unary operators
		;	ch = bit mask of required bytes,
		;	 bits 0..3 represent one byte of a dword each
		;	 bits 4..7 are clear
		; CHG:	bx, ch, di
		;
		; Type specifications are parsed as unary operators
		; here. (Elsewhere, "unary operators" refers only to
		; the unary operators specified as one of "+-~!?".)
count_unary_operators: section_of_function
	xor di, di
		; INP:	si-> possible unary operators
		;	di = maximum count+1 of unary operators to process,
		;	      zero means unlimited
		; OUT:	dx = 1 + count of unary operators,
		;	      at most di
		;	al, si-> behind identified unary operators
		;	ch = bit mask of required bytes,
		;	 bits 0..3 represent one byte of a dword each
		;	 bits 4..7 are clear
		;	ZR if maximum reached
		;	NZ if maximum not reached
		; CHG:	bx, ch
count_unary_operators_restrict: section_of_function
	mov ch, 1111b		; default to access a full dword
	xor dx, dx		; initialize counter to zero
	jmp @F

.loop:
	nearcall skipcomma
	dec si
	pop di			; get maximum count
@@:
	inc dx			; count unary operators and type specifiers
	push di			; save maximum count again
	nearcall skipwhite	; load next character and skip blanks
	cmp dx, di		; reached maximum ?
	je .end			; yes --> (ZR)
	 push cx
	nearcall istype?	; check for type and if so retrieve info
	jc .notype		; not a type -->
	 pop cx

	shr bx, 1		; discard signedness bit
	mov si, di		; -> behind the type specifier
	mov cl, 01Fh		; prepare shift count register
	and cx,[typebitmasks+bx]; apply mask and get shift count register
	shl ch, cl		; apply shift
	jmp short .loop		; check for more -->

.notype:
	nearcall isunaryoperator?; is it a unary operator?
	 pop cx
	je .loop		; yes, check for more -->
				; (NZ)
.end:
	pop di			; discard
	retn


get3byte.checksignificantbits: section_of_function
	push ax
	and ah, 3Fh
	cmp ah, 24
	jmp checksignificantbitscommon

getword: section_of_function
	push bx
	nearcall getexpression
	pop bx
.checksignificantbits: section_of_function getword
	push ax
	and ah, 3Fh
	cmp ah, 16
	jmp checksignificantbitscommon

getbyte: section_of_function
	push bx
	push dx
	nearcall getexpression
	pop bx
	mov dh, bh
	pop bx
.checksignificantbits: section_of_function getbyte
	push ax
	and ah, 3Fh
	cmp ah, 8
checksignificantbitscommon: section_of_function
	pop ax
	ja short errorj6	; if error
	retn

errorj6:
	jmp error_expr


;	GETNYB - Convert the hex character in AL into a nybble.  Return
;	carry set in case of error.

getnyb: section_of_function
	push ax
	sub al, '0'
	cmp al, 9
	jbe .return		; if normal digit
	pop ax
	push ax
	nearcall uppercase
	sub al, 'A'
	cmp al, 'F'-'A'
	ja .error		; if not A..F
	add al, 10
.return:
	inc sp			; normal return (first pop old AX)
	inc sp
	clc
	retn
.error:
	pop ax			; error return
	stc
	retn


		; INP:	si -> input line
		; OUT:	NC if normal range (nonzero, upper >= lower),
		;	 bx:dx = TO value (upper bound)
		;	 cx:di = FROM value (lower bound)
		;	CY if not normal,
		;	 bx:dx and cx:di may be invalid
		;	 ZR if FROM LENGTH with zero length, cx:di valid
		;	al = first character
		;	si -> next character
		;	jumps to error if invalid input
get_value_range: section_of_function
	nearcall skipwhite
	dec si
	mov dx, msg.from
	nearcall isstring?
	lodsb
	jne .not_from

.from:
	nearcall getexpression	; (recursive)
	mov di, dx
	mov cx, bx		; cx:di = from

	nearcall skipcomm0
	dec si
	mov dx, msg.to
	nearcall isstring?
	je .from_to
	mov dx, msg.length
	nearcall isstring?
	jne short .error

.from_length:
	lodsb
	push cx
	nearcall get_length	; (recursive call to getexpression)
	pop cx
	test bx, bx
	jnz @F
	test dx, dx
	jnz @F
				; ZR = length is zero, cx:di = from
.notnormal:
	stc
	retn

.error:
	jmp error_expr

@@:
				; bx:dx = length
	add dx, di
	adc bx, cx		; bx:dx = from + length
	sub dx, 1
	sbb bx, 0		; bx:dx = from + length - 1
	jmp @F

.from_to:
	lodsb

	nearcall getexpression	; (recursive)
				; bx:dx = to
				; cx:di = from
	jmp @F

.not_from:
	nearcall getexpression	; (recursive)
	mov di, dx
	mov cx, bx		; bx:dx = cx:ax = value

@@:
	cmp bx, cx
	jne @F
	cmp dx, di
@@:
	jb .notnormal		; NZ = length overflow or not normal FROM TO
	retn			; (NC)


stack_check_indirection:
	mov ax, _EXPRESSION_INDIRECTION_STACK_CHECK

		; INP:	ax = how much stack should be left
		;	word [cs:ip + 1] = message for location
		; OUT:	doesn't return if stack overflow
		; CHG:	ax
		; STT:	ds = ss
stack_check:
%if _SYMBOLIC
	call .internal
%else
	add ax, stack
	cmp sp, ax
%endif
	jb @F
	retn

@@:
	pop ax

	push ss
	pop es
	mov sp, [throwsp]

	mov di, msg.stack_overflow.caller
	nearcall hexword
	mov dx, msg.stack_overflow
	nearcall putsz
	xchg ax, bx
	mov dx, [cs:bx + 1]
	nearcall putsz

%if _EXPRDUALCODE && _DUALCODE
	nearcall to_errret_pop
	usesection lDEBUG_CODE

to_errret_pop: equ $
	add sp, 6
	jmp near [errret]
	usesection lDEBUG_CODE2
%else
	jmp near [errret]
%endif


%if _SYMBOLIC
		; INP:	ax = how much stack should be left
		; OUT:	CY if stack overflow
		; CHG:	ax
		; STT:	ds = ss
.internal: section_of_function stack_check
	add ax, word [stack_low_address]
	cmp sp, ax
	retn
%endif

%if _PM
		; INP:	ss:sp -> 8-byte save area
		; OUT:	in PM: save area filled
		;	save area left on stack
		; CHG:	-
		; STT:	es = ds = debugger data selector
save_scratchsel:
	lframe near
	lpar qword, savearea
	lpar_return
	lenter
	nearcall ispm
	jnz .ret
	push ax
	push bx
	_386_o32
	push di
_386	xor edi, edi
	lea di, [bp + ?savearea]
	mov bx, word [scratchsel]
	mov ax, 000Bh
	int 31h			; get descriptor
	_386_o32
	pop di
	pop bx
	pop ax
.ret:
	lleave
	lret

		; INP:	ss:sp -> 8-byte save area
		; OUT:	in PM: save area used
		;	save area popped from stack
		; CHG:	-
		; STT:	es = ds = debugger data selector
restore_scratchsel:
	lframe near
	lpar qword, savearea
	lenter
	nearcall ispm
	jnz .ret
	push ax
	push bx
	_386_o32
	push di
_386	xor edi, edi
	lea di, [bp + ?savearea]
	mov bx, word [scratchsel]
	mov ax, 000Ch
	int 31h			; set descriptor
	_386_o32
	pop di
	pop bx
	pop ax
.ret:
	lleave
	lret
%endif
