
%if 0

lDebug B commands (permanent breakpoints, break upwards)

Copyright (C) 2008-2026 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

%define SOURCEFILENAME "bb.asm"
%ifidni __OUTPUT_FORMAT__, obj
 %include "debug.mac"
%endif


	usesection lDEBUG_DATA_ENTRY
%if _LINK_COMPAT
	align 2, db 0
%endif

bb_dispatch:
.scan:
%if _BREAKPOINTS
	db 'C'
	db 'D'
	db 'E'
	db 'T'
	db 'P'
	db 'N'
	db 'L'
	db 'I'
	db 'W'
 %if BPSIZE == 6 || BPSIZE == 9
	db 'O'
 %endif
	db 'S'
%endif
	db 'U'		; BU command
	db 0		; placeholder, never matches
.scanamount: equ $ - .scan

		; REM:	Dispatch table in section lDEBUG_CODE
	align 2, db 0
.offset:
%if _BREAKPOINTS
	dw point_clear_enable_disable_toggle_common	; C
	dw point_clear_enable_disable_toggle_common	; D
	dw point_clear_enable_disable_toggle_common	; E
	dw point_clear_enable_disable_toggle_common	; T
	dw point_set	; P
	dw point_number	; N
	dw point_list	; L
	dw point_id	; I
	dw point_when	; W
 %if BPSIZE == 6 || BPSIZE == 9
	dw point_offset	; O
 %endif
	dw point_swap	; S
%endif
	dw bu_breakpoint; U
	dw error_at_bx	; if none matched
.offsetamount: equ ($ - .offset) / 2

%if .scanamount != .offsetamount
 %error bb dispatch mismatch
%endif

	align 2, db 0
.di:
%if _BREAKPOINTS
	dw point_clear	; C
	dw point_disable; D
	dw point_enable	; E
	dw point_toggle	; T
%endif

	usesection lDEBUG_CODE
%if _LINK_COMPAT
	align 2, db 0
%endif

bb:
%if _BOOTLDR
 %if _APPLICATION || _DEVICE
	testopt [internalflags], nodosloaded
	jz @F
 %endif
	dec si
	dec si			; -> at 'B'
	mov dx, msg.boot
	call isstring?		; check for "BOOT"
	je bootcmd
	inc si			; skip 'B'
	lodsb			; load next
@@:
%endif
	call capitalise
	mov di, bb_dispatch.scan
	mov cx, bb_dispatch.scanamount
	repne scasb
				; di -> behind the NUL if no valid subcommand
	sub di, bb_dispatch.scan + 1
				; get 0-based index
	shl di, 1		; byte index to word index
	mov bx, si
	call skipcomma		; (most commands shared this)
	push word [bb_dispatch.offset + di]
				; dispatch into (near word) table
	mov di, word [bb_dispatch.di + di]
	retn			; jump to routine

error_at_bx:
	mov si, bx
	jmp error


%if _BREAKPOINTS
point_set:
	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	mov di, .get_saved	; access saved address later
	call findpointat	; do we find it ?
	jc .find_new		; no, treat as if "NEW" keyword given -->
				; point index is in dx
	push dx			; stack = point index
	jmp @FF			; skip check whether used

.not_at:
	mov di, .get_addr	; get address from input command line later
	call getpointindex
	jnc @F			; got an index -->
	jz error		; "ALL" is invalid
				; got "NEW" keyword
.find_new:
	xor cx, cx		; loop counter = 0 (start with point index 0)
	push ax			; stack = next text byte
.new_loop:
	mov ax, cx		; try this index
	call calcpointbit	; bx, ah = offset and mask
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jz .new_found		; found unused one -->

	inc cx			; = next index
	cmp cx, _NUM_B_BP	; valid ?
	jb .new_loop		; yes, try next -->

	mov dx, msg.bb_no_new	; no free point found
	mov ax, 0108h
	call setrc
	jmp prnquit


		; INP:	al=, si-> input line
		; OUT:	al=, si-> after
		;	bx:dx = linear adddress
		;	does not return if error
		; CHG:	edxh
.get_addr:
	mov bx, word [reg_cs]	; default seg/sel is cs
	call getlinearaddr	; parse address (segmented or "@ " linear)
	jnc .retn		; success -->
	jmp error


		; INP:	al=, si-> input line
		; OUT:	bx:dx = linear address
		; CHG:	-
.get_saved:
	mov dx, word [..@bb_saved_linear]
	mov bx, word [..@bb_saved_linear + 2]
.retn:
	retn


.new_found:
	pop ax			; restore text byte in al
	push cx			; stack = point index
	jmp @FF

@@:
	push dx			; preserve point index
	push ax
	mov ax, dx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	pop ax
	jnz error
@@:
	call di			; call either .get_addr or .get_saved
				; in any case, bx:dx = linear address
	mov di, 8000h		; default counter
	push dx			; preserve linear low word
	xor dx, dx

	mov word [..@bb_id_length], dx
	mov word [..@bb_when_length], dx
				; initialise to empty ID and WHEN condition
	push dx			; stack = 0 initially
.loop_additional:
	call skipcomm0
	dec si
	mov dx, msg.number
	call isstring?
	je .additional_number
	mov dx, msg.counter
	call isstring?
	je .additional_number
	mov dx, msg.id
	call isstring?
	je .additional_id
	mov dx, msg.when
	call isstring?
	je .additional_when
%if BPSIZE == 6 || BPSIZE == 9
	mov dx, msg.offset
	call isstring?
	je .additional_offset
%endif
	lodsb
	call iseol?
	je .no_additional
	pop dx			; get stack (0 if no number yet)
	test dx, dx		; already got a number without keyword?
	jnz error		; yes -->
	inc dx			; remember for subsequent iterations
	push dx			; stack = nonzero
	call skipwh0
	jmp @F

.additional_number:
	call skipequals
	call iseol?
	je error
@@:
	call getcounter
	jmp .loop_additional

%if BPSIZE == 6 || BPSIZE == 9
.additional_offset:
	pop dx
	or dl, 1		; remember for subsequent iterations
				;  not to accept number without keyword
	push dx
	call skipequals
%if _PM
	push bx
	nearcall getdword
	mov word [bp_offset], dx
	mov word [bp_offset + 2], bx
	pop bx
%else
	nearcall getword
	mov word [bp_offset], dx
%endif
	jmp .loop_additional
%endif

.additional_when:
	pop dx
	or dl, 1		; remember for subsequent iterations
				;  not to accept number without keyword
	push dx
	call skipequals
	dec si
	call get_when
	jmp .loop_additional

.additional_id:
	call skipequals
	dec si
	call get_id

.no_additional:
	pop dx			; discard non-keyword NUMBER indicator
	pop dx			; restore dx = low word of linear

	xchg bx, dx		; dx:bx = linear
	xchg bx, ax		; dx:ax = linear
	pop bx			; = 0-based point index to set
	push dx
	push ax			; on stack: dword linear

	push di			; stack = counter
	xchg dx, bx		; dx = point index

		; As for set_id, set_when will free a prior condition
		;  when writing the new one. However, we check for the
		;  appropriate buffer size being still free before
		;  calling set_when because we want to cancel the point
		;  initialisation if either the ID or the condition do
		;  not fit, without having yet written anything.
	call check_when_space	; CHG ax, bx, cx, si, di

		; Note that point_clear and init both leave the
		;  empty word in the ID array. Therefore we can
		;  always handle this by freeing the prior value
		;  first, which is required if we're resetting
		;  an existing point with BP AT.
		; The set_id function takes care of this.
	call set_id		; CHG ax, bx, cx, si, di

		; After check_when_space then set_id both returned,
		;  we have finally checked all error conditions and
		;  are now actually modifying things.
	call set_when		; CHG ax, bx, cx, si, di
	xchg ax, dx		; ax = point index

	mov bx, ax
	add bx, bx		; * 2
	pop word [ b_bplist.counter + bx ]
				; = counter value
	add bx, bx		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add bx, ax		; * 5
%elif BPSIZE == 6
	add bx, ax		; * 5
	add bx, ax		; * 6
%elif BPSIZE == 9
	add bx, bx		; * 8
	add bx, ax		; * 9
%else
 %error Unexpected breakpoint size
%endif
	pop word [ b_bplist.bp + bx ]
		; These two instructions need to stay in that order.
		; For the non-PM version, the pop overwrites the byte
		; that is then initialized to 0CCh (the breakpoint
		; content byte).
		; (This is not true for BPSIZE == 6. Instead, the pop
		;  overwrites the first byte of the preferred offset.)
	pop word [ b_bplist.bp + bx + 2 ]
	mov byte [ b_bplist.bp + bx + BPSIZE - 1 ], 0CCh
%if BPSIZE == 6
	push word [bp_offset]
	pop word [ b_bplist.bp + bx + 3 ]
%elif BPSIZE == 9
	push word [bp_offset]
	pop word [ b_bplist.bp + bx + 4 ]
	push word [bp_offset + 2]
	pop word [ b_bplist.bp + bx + 6 ]
%endif
	call calcpointbit	; bx, ah = offset and mask
%if ((_NUM_B_BP+7)>>3) != 1
	or byte [b_bplist.used_mask+bx], ah
				; mark as used
	not ah
	and byte [b_bplist.disabled_mask+bx], ah
				; clear disabled flag
 %if _BREAKPOINTS_STICKY
	and byte [b_bplist.sticky_mask+bx], ah
 %endif
%else
	or byte [b_bplist.used_mask], ah
	not ah
	and byte [b_bplist.disabled_mask], ah
 %if _BREAKPOINTS_STICKY
	and byte [b_bplist.sticky_mask], ah
 %endif
%endif
	retn			; done


		; INP:	si -> first non-blank character
		; OUT:	..@bb_id_start and ..@bb_id_length set
		;	does not return if error (too long)
		; CHG:	ax, cx, si
get_id:
	mov word [..@bb_id_start], si
				; -> text to use
@@:
	lodsb			; load next text byte
	call iseol?.notsemicolon; end reached ?
	jne @B			; not yet -->
		; si -> after EOL char
		; si - 1 -> EOL char
@@:
	dec si			; -> past text to use
	cmp si, word [..@bb_id_start]
				; start reached ?
	je @F			; yes, avoid underflow -->
	cmp byte [si - 1], 32	; last byte is a blank ?
	je @B
	cmp byte [si - 1], 9
	je @B			; yes, decrement offset -->
@@:
	mov cx, si		; -> past text
	sub cx, word [..@bb_id_start]
				; = length of text to use
	mov word [..@bb_id_length], cx
				; store the length
	cmp cx, 63		; may fit ?
	ja error		; no -->

%if 0
	push dx
	mov dx, msg.id
	call putsz
	mov al, 32
	call putc
	mov al, '"'
	call putc
	mov dx, word [..@bb_id_start]
	mov cx, word [..@bb_id_length]
	call disp_message_length_cx
	mov al, '"'
	call putc
	mov dx, crlf
	call putsz
	pop dx
%endif
	retn


		; INP:	si -> first non-blank character
		; OUT:	..@bb_when_start and ..@bb_when_length set
		;	does not return if error
		;	al = character after the condition, si -> next
		; CHG:	ax, cx, si
get_when:
	mov word [..@bb_when_start], si
				; -> text to use
@@:
	lodsb
	push dx
	push bx
	nearcall getexpression	; parse the number
	pop bx
	pop dx
		; si -> after next nonblank
		; si - 1 -> next nonblank
@@:
	dec si			; -> past text to use
	cmp si, word [..@bb_when_start]
				; start reached ?
	je @F			; yes, avoid underflow -->
				;  (cannot actually be reached)
	cmp byte [si - 1], 32	; last byte is a blank ?
	je @B
	cmp byte [si - 1], 9
	je @B			; yes, decrement offset -->
@@:
	mov cx, si		; -> past text
	sub cx, word [..@bb_when_start]
				; = length of text to use
	mov word [..@bb_when_length], cx
				; store the length

	lodsb
%if 0
	push dx
	mov dx, msg.when
	call putsz
	mov al, 32
	call putc
	mov al, '"'
	call putc
	mov dx, word [..@bb_when_start]
	mov cx, word [..@bb_when_length]
	call disp_message_length_cx
	mov al, '"'
	call putc
	mov dx, crlf
	call putsz
	dec si
	lodsb
	pop dx
%endif
	retn


		; INP:	al = first character, si -> next character
		;	di = default value
		; OUT:	di = counter value (default if EOL)
		;	al = first character after number, si -> next
		; CHG:	-
		;	does not return if error encountered
getcounter:
.:
	call skipwh0
	call iseol?
	je .got_counter
	push dx
	nearcall getword
	mov di, dx
	pop dx
.got_counter:
	retn


		; INP:	al = first character, si -> next character
		; OUT:	di = counter value (defaults to 8000h)
		; CHG:	ax, si (flags not changed)
		;	does not return if error encountered
.pushf_chkeol:
	mov di, 8000h		; default counter
	pushf
	push dx
	dec si			; -> text
	mov dx, msg.number	; NUMBER= keyword ?
	call isstring?
	je @F
	mov dx, msg.counter	; COUNTER= keyword ?
	call isstring?
	jne @FF			; no -->
@@:
	call skipequals		; yes, eat optional equals sign
	db __TEST_IMM8		; (skip lodsb)
@@:
	lodsb
	pop dx
	call .			; get the counter
	call chkeol		; make sure we end line
	popf
	retn


point_number:
	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	call findpointat	; do we find it ?
	jc error		; not found -->
	call getcounter.pushf_chkeol
	jmp @F			; point index is in dx -->

.not_at:
	call getpointindex
	call getcounter.pushf_chkeol
	jnc @F
	jnz error		; "NEW" is invalid -->

	xor cx, cx
.all_loop:
	mov ax, cx
	mov dx, cx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jz .all_next		; skip if unused -->
	call .setnumber
.all_next:
	inc cx			; next point
	cmp cx, _NUM_B_BP	; more to go ?
	jb .all_loop		; yes -->
	retn			; done

@@:
	mov ax, dx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif				; in use ?
	jz error		; no -->

.setnumber:
	mov bx, dx		; = index
	add bx, bx		; * 2, = word index
	mov word [b_bplist.counter + bx], di
	retn


point_id:
	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	call findpointat	; do we find it ?
	jc error		; not found -->
	jmp @F			; point index is in dx -->

.not_at:
	call getpointindex
	jc error		; "NEW" and "ALL" keywords are invalid -->

@@:
	push ax			; preserve text
	mov ax, dx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jz error
	pop ax			; restore al = text

	call skipcomm0

	dec si			; si -> nonblank
	push dx
	mov dx, msg.id		; "ID=" keyword ?
	call isstring?
	jne .no_id_kw		; no -->
	call skipequals		; skip optional equals sign
	dec si			; si -> nonblank
.no_id_kw:
	pop dx
	call get_id		; set ID variables

		; INP:	..@bb_id_start
		;	..@bb_id_length
		;	dx = point index
		; OUT:	jumps to error if failure (too long)
		; CHG:	ax, bx, cx, si, di
set_id:
	mov bx, -1		; just get, do not set
	call get_set_id_offset_length
	mov cl, 10
	mov ax, bx
	shr bx, cl		; bx = length of ID to free

	mov si, word [b_bplist.idbuffer.free]
				; displacement free
	neg si			; - displacement free
	add si, _NUM_B_ID_BYTES
				; 1024 - displacement free = amount free
	add si, bx		; amount free + length of ID to free
	mov cx, word [..@bb_id_length]
	jcxz .empty		; if no ID to set -->
	cmp si, cx		; enough free ?
	mov si, word [..@bb_id_start]
				; -> ID string
	jb error		; no -->

	push cx
	call free_id		; actually free it now
	pop cx
	push cx
	mov bx, cx		; length
	mov cl, 10		; offset part is 10 bits (0..1023)
	shl bx, cl		; length is in top 6 bits (0..63)
	pop cx
	mov di, word [b_bplist.idbuffer.free]
				; = displacement of free part
	add word [b_bplist.idbuffer.free], cx
				; mark space as used
	or bx, di		; OR in the offset
	add di, b_bplist.idbuffer
				; -> into buffer space
	rep movsb		; write

				; now remember this
.after_empty:
		; INP:	dx = 0-based point index
		;	bx = word to set (-1 if not to modify)
		; OUT:	bx = word read
get_set_id_offset_length: equ $
	xchg dx, bx		; bx = index, dx = to set
	shl bx, 1		; bx = word index (undone later)
	push word [b_bplist.id + bx]
				; stack = get it
	cmp dx, -1		; get only ?
	je @F			; yes -->
	mov word [b_bplist.id + bx], dx
				; set it
@@:
	pop dx			; get it from stack
	shr bx, 1		; undo to get back (byte) index
	xchg dx, bx
	retn

.empty:
	call free_id
	xor bx, bx		; offset = 0 and length = 0
	jmp .after_empty


		; INP:	ax = offset/length word of ID to free
		;	 (length zero means none)
		;	b_bplist.id = ID array (ONE of which matches ax)
		; CHG:	ax, bx, cx
		; OUT:	b_bplist.id entries adjusted
		;	 (the one that is being freed is unaffected)
		;	b_bplist.idbuffer adjusted
		; STT:	UP, ss = ds = es
		; REM:	The b_bplist.id array contains zeroes as
		;	 indicators of unused entries. This implies
		;	 that the length field is zero too. However,
		;	 the canonical NULL entry is *all* zeros.
free_id:
	mov cl, 10
	mov bx, ax
	and bh, 1023 >> 8	; bx = offset of ID to free
	shr ax, cl		; ax = length of ID to free
	xchg cx, ax		; cx = length of ID to free

	push si
	push di

	jcxz .return		; if none to free -->

	 push cx
	lea di, [b_bplist.idbuffer + bx]
				; -> ID to be freed
				;  (destination of following data)
	mov si, di
	add si, cx		; -> behind ID to be freed
				;  (source of following data)
	mov cx, si
	neg cx			; minus pointer to first subsequent data
	add cx, b_bplist.idbuffer + _NUM_B_ID_BYTES
				; pointer behind buffer - pointer subsequent data
				;  = length of data to move
	rep movsb		; now di -> first uninitialised byte
	 pop cx			; = length of data freed

	sub word [b_bplist.idbuffer.free], cx
				; mark as free
	push cx
	xor al, al
	rep stosb		; clear the buffer trailer (uninitialised part)
	pop di			; di = length of data freed

	mov si, b_bplist.id
%if _NUM_B_BP < 256
	mov cl, _NUM_B_BP
%else
	mov cx, _NUM_B_BP
%endif
.loop:
	lodsw
	and ax, 1023		; get offset
	cmp ax, bx		; offset matches what we're freeing ?, OR
				;  is it below/equal the offset we're freeing ?
	jbe .next		; yes --> (also jumps if ax == 0)
	sub word [si - 2], di	; adjust offset
		; This subtraction shouldn't underflow the 10 bits
		;  used for the offset, so it should leave the top
		;  6 bits for the ID length unchanged.
.next:
	loop .loop

.return:
	pop di
	pop si
	retn


%if BPSIZE == 6 || BPSIZE == 9
point_offset:
	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	call findpointat	; do we find it ?
	jc error		; not found -->
	jmp @F			; point index is in dx -->

.not_at:
	call getpointindex
	jc error		; "NEW" and "ALL" keywords are invalid -->

@@:
	push ax
	mov ax, dx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jz error
	pop ax

	call skipcomm0
	push dx			; stack = point index
	mov dx, -1		; all-1s pattern to uninit offset
%if BPSIZE == 9
	mov bx, dx		; upper word of all-1s pattern
%endif
	call iseol?
	je @F			; uninit it -->
	dec si
	mov dx, msg.offset
	call isstring?
	jne .no_offset_kw
	call skipequals
	dec si
.no_offset_kw:
	lodsb
%if BPSIZE == 9
	nearcall getdword	; bx:dx = offset
%else
	nearcall getword	; dx = offset
%endif
	call chkeol
@@:
	pop ax			; = point index
	mov di, ax
	add di, di		; * 2
	add di, di		; * 4
%if BPSIZE == 6
	add di, ax		; * 5
	add di, ax		; * 6
	add di, b_bplist.bp + 3	; +3 = skip 24-bit linear address
%elif BPSIZE == 9
	add di, di		; * 8
	add di, ax		; * 9
	add di, b_bplist.bp + 4	; +4 = skip 32-bit linear address
%else
 %error Unexpected breakpoint size
%endif
	xchg ax, dx
	stosw			; store low word of offset
%if BPSIZE == 9
	xchg ax, bx
	stosw			; store high word of offset
%endif
	retn			; done
%endif


point_when:
	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	call findpointat	; do we find it ?
	jc error		; not found -->
	jmp @F			; point index is in dx -->

.not_at:
	call getpointindex
	jc error		; "NEW" and "ALL" keywords are invalid -->

@@:
	push ax			; preserve text
	mov ax, dx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jz error
	pop ax			; restore text

	call skipcomm0
	and word [..@bb_when_length], 0
				; init variable to empty (length = 0)
	call iseol?		; EOL ?
	je @F			; yes, set immediately -->
	dec si			; -> nonblank
	push dx			; stack = point index
	mov dx, msg.when	; "WHEN=" keyword ?
	call isstring?
	jne .no_when_kw		; no -->
	call skipequals		; skip optional equals sign
	dec si			; -> nonblank
.no_when_kw:
	pop dx			; = point index
	call get_when		; set WHEN variables
	call chkeol
@@:


		; INP:	..@bb_when_start
		;	..@bb_when_length
		;	dx = point index
		; OUT:	jumps to error if failure (too long)
		; CHG:	ax, bx, cx, si, di
		; STT:	UP, ss = ds = es
set_when:
	call check_when_space	; cx = length (with terminating NUL) or 0,
				;  si -> clause (if cx != 0),
				;  ax = prior pointer or 0
	jcxz .empty

	push cx
	call free_when		; actually free it now (INP ax)
	pop cx
	mov di, word [b_bplist.whenbuffer.free]
				; = displacement of free part
	add word [b_bplist.whenbuffer.free], cx
				; mark space as used
	add di, b_bplist.whenbuffer
				; -> into buffer space
	mov bx, di		; bx -> buffer for clause, for set function
				; si -> new clause (left by check function)
	rep movsb		; write (with space for the NUL)
	mov byte [di - 1], cl	; actually write a NUL

				; now remember this
.after_empty:
		; INP:	dx = 0-based point index
		;	bx = word to set (-1 if not to modify)
		; OUT:	bx = word read
get_set_when_offset: equ $
	xchg dx, bx		; bx = index, dx = to set
	shl bx, 1		; bx = word index (undone later)
	push word [b_bplist.when + bx]
				; stack = get it
	cmp dx, -1		; get only ?
	je @F			; yes -->
	mov word [b_bplist.when + bx], dx
				; set it
@@:
	pop dx			; get it from stack
	shr bx, 1		; undo to get back (byte) index
	xchg dx, bx
	retn

.empty:
	call free_when
	xor bx, bx		; offset = 0
	jmp .after_empty


		; INP:	..@bb_when_start
		;	..@bb_when_length
		;	dx = point index
		; OUT:	jumps to error if failure (too long)
		;	ax = prior pointer from array (to be freed),
		;	 or 0 if no prior clause to free
		;	cx = length (including terminating NUL)
		;	 or = 0 if no WHEN clause
		;	(if cx != 0) si -> WHEN clause data
		; CHG:	ax, bx, cx, si, di
		; STT:	UP, ss = ds = es
check_when_space:
	mov bx, -1
	call get_set_when_offset

	push bx
	test bx, bx		; any ?
	jz @F			; no -->
	mov di, bx		; -> current condition
	mov cx, -1
	mov al, 0		; NUL
	repne scasb		; scan it
	not cx			; = length to free (including terminating NUL)
	mov bx, cx
@@:				; bx = length to free
	pop ax			; ax -> prior clause in .whenbuffer (or 0)

	mov si, word [b_bplist.whenbuffer.free]
				; displacement free
	neg si			; - displacement free
	add si, _NUM_B_WHEN_BYTES
				; 1024 - displacement free = amount free
	add si, bx		; amount free + length of condition to free
	mov cx, word [..@bb_when_length]
	jcxz .empty		; if no condition to set -->
	inc cx			; count terminating NUL
	cmp si, cx		; enough free ?
	mov si, word [..@bb_when_start]
				; -> condition string
	jb error		; no -->
.empty:
	retn


		; INP:	ax = offset word of condition to free
		;	 (zero means none)
		;	b_bplist.when = condition array (ONE of which matches ax)
		; CHG:	ax, bx, cx
		; OUT:	b_bplist.when entries adjusted
		;	 (the one that is being freed is unaffected)
		;	b_bplist.whenbuffer adjusted
		; STT:	UP, ss = ds = es
		; REM:	The b_bplist.when array contains actual offsets
		;	 into the b_bplist.whenbuffer space. Therefore
		;	 a value of zero acts as a NULL pointer and valid
		;	 values are >= b_bplist.whenbuffer.
free_when:
	push si
	push di

	test ax, ax		; nothing stored ?
	jz .return		; yes, just return -->

	mov di, ax
	mov bx, ax
				; -> condition to be freed
				;  (destination of following data)
	push di
	mov cx, -1
	mov al, 0		; NUL
	repne scasb		; scan it
	not cx			; = length to free (including terminating NUL)
	pop di			; -> destination of following data
	 push cx		; length to free
	mov si, di
	add si, cx		; -> behind condition to be freed
				;  (source of following data)
	mov cx, si
	neg cx			; minus pointer to first subsequent data
	add cx, b_bplist.whenbuffer + _NUM_B_WHEN_BYTES
				; pointer behind buffer - pointer subsequent data
				;  = length of data to move
	rep movsb		; now di -> first uninitialised byte
	 pop cx			; = length of data freed

	sub word [b_bplist.whenbuffer.free], cx
				; mark as free
	push cx
	xor al, al
	rep stosb		; clear the buffer trailer (uninitialised part)
	pop di			; di = length of data freed

	mov si, b_bplist.when	; -> list of words pointing into WHEN buffer
%if _NUM_B_BP < 256
	mov cl, _NUM_B_BP	; cx = number of points
%else
	mov cx, _NUM_B_BP
%endif
.loop:
	lodsw
	cmp ax, bx		; offset we're freeing ?, OR
				;  is it below/equal the offset we're freeing ?
	jbe .next		; yes --> (also jumps if ax == 0)
	sub word [si - 2], di	; adjust offset
.next:
	loop .loop

.return:
	pop di
	pop si
	retn


point_clear:
	not ax
%if ((_NUM_B_BP+7)>>3) != 1
	and byte [b_bplist.used_mask+bx], ah
	and byte [b_bplist.disabled_mask+bx], ah
%else
	and byte [b_bplist.used_mask], ah
	and byte [b_bplist.disabled_mask], ah
%endif
	push cx

	xor bx, bx		; replace by empty word
	call get_set_id_offset_length
	xchg ax, bx		; ax = word what to free
	call free_id		; actually free it now

	xor bx, bx		; replace by empty word
	call get_set_when_offset
	xchg ax, bx		; ax = word what to free
	call free_when		; actually free it now

%if 0
	xor cx, cx

	mov bx, dx
	add bx, bx		; * 2
	mov word [b_bplist.counter + bx], cx

	add bx, bx		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add bx, dx		; * 5
%elif BPSIZE == 6
	add bx, dx		; * 5
	add bx, dx		; * 6
%elif BPSIZE == 9
	add bx, bx		; * 8
	add bx, dx		; * 9
%else
 %error Unexpected breakpoint size
%endif
	add bx, b_bplist.bp
	mov word [bx], cx
	mov word [bx + 2], cx
%if BPSIZE == 4
%elif BPSIZE == 5
	mov byte [bx + 4], cl
%elif BPSIZE == 6
	mov word [bx + 4], cx
%elif BPSIZE == 9
	mov word [bx + 4], cx
	mov word [bx + 6], cx
	mov byte [bx + 8], cl
%else
 %error Unexpected breakpoint size
%endif

%endif

	pop cx
	retn

point_clear_enable_disable_toggle_common:
		; di -> bb handler
		; INP:	dx = point index
		;	bx = offset into bit arrays
		;	ah = mask for bit arrays
		; CHG:	ax, bx

		; bp -> handle next point in dx (if not "ALL" keyword)
		; INP:	di = bb handler, call eventually
		;	al = text, si -> subsequent text
		;	dx = from below function, or point index
		;	cx = from below function, or 1
		;	branch to .in to continue looping,
		;	 al = text, si -> subsequent text
		;	return near if second loop is done
		;	in first loop only:
		;	 ss:sp -> word start of list
		;	in second loop:
		;	 ss:sp -> near return address in cmd4
		; REM:	This is called to handle the next point.
		;	 Should loop to .in to continue parsing
		;	 if more IN values follow.
		;	These form two IN loops: After the only
		;	 parsing is done si-> is restored and the
		;	 doing (execution of bb handler) starts.
		;	For non-IN this just handles the point
		;	 and immediately returns to the caller.

		; word [cs:bp - 2] -> parse index of next point
		; INP:	di = bb handler, preserve
		;	al = text, si -> subsequent text
		; OUT:	NC if no keyword (always if IN),
		;	 dx = point index
		;	 (cx = number of points if IN)
		;	CY if keyword,
		;	 NZ if "NEW"
		;	 ZR if "ALL"
		;	al = text, si -> subsequent text
		; REM:	This is called first, in the main loop,
		;	 if an "AT" keyword wasn't used. Outputs
		;	 have to be valid for bp -> function if
		;	 the return is NC.
	mov bp, .checkend
	dec si			; -> nonblank
	mov dx, msg.in		; "IN" keyword ?
	call isstring?
	jne .notin		; no -->
	mov bp, .parse_next	; set up for IN list
	call skipcomma
	dec si
	mov dx, msg.existing	; "EXISTING" keyword ?
	call isstring?
	lodsb
	jne .notexisting	; no -->
	mov bp, .parse_next_existing
				; set up for IN EXISTING list
	call skipcomm0
.notexisting:
	push si			; stack -> start of list
	jmp .in

		; INP:	di = preserve
		;	si -> subsequent text
		;	si - 1 -> text
		; OUT:	NC,
		;	 dx = point start
		;	 cx = amount points
		;	branches to error on invalid specification
.in_multiple:			; parse an IN point spec
	push di
	dec si			; si -> nonblank
	nearcall get_value_range; OUT:	cx:di = from, bx:dx = to
	jnc @F			; normal value range -->
	jnz .error		; NZ if nonzero length -->
	cmp di, _NUM_B_BP	; start in range ?
	jae .error		; no -->
	jcxz .in_multiple.next	; yes --> (cx = 0)
.error:
	jmp error

@@:
	cmp di, _NUM_B_BP	; start in range ?
	jae .error
	jcxz @F
	jmp .error		; no -->

@@:
	test bx, bx		; end in range ?
	jnz .error
	cmp dx, _NUM_B_BP
	jae .error		; no -->

	mov cx, dx		; = end
	sub cx, di		; = end - start = amount - 1
	inc cx			; = amount (nonzero)
.in_multiple.next:
	mov dx, di		; dx = first index
	pop di
	clc			; no keywords
	retn


	align 2, db 0
	dw .in_multiple
.do_next_existing:
	jcxz @FFF		; value range with length zero ?
@@:
	call .checkindex_internal
	jz @F			; skip unused point -->
	call di			; has to preserve cx, dx, di, si, bp
@@:
	inc dx
	loop @BB		; loop through value range
@@:
	dec si
	call skipcomma
	call iseol?		; EOL reached ?
	jne .in			; not yet -->
	retn			; yes, done

	align 2, db 0
	dw .in_multiple
.parse_next_existing:
		; REM:	no check done here, any return from
		;	 the .in_multiple value range is ok.
	dec si
	call skipcomma
	call iseol?		; EOL reached ?
	jne .in			; not yet -->

	pop si			; restore -> start of list
	mov bp, .do_next_existing
				; next do them
	jmp .in			; and loop again

	align 2, db 0
	dw .in_multiple
.do_next:
	jcxz @FFF		; value range with length zero ?
@@:
	call .checkindex	; error if invalid/unused point
	call di			; has to preserve cx, dx, di, si, bp
@@:
	inc dx			; next point
	loop @BB		; loop through value range
@@:
	dec si
	call skipcomma
	call iseol?		; EOL reached ?
	jne .in			; not yet -->
	retn			; done

	align 2, db 0
	dw .in_multiple
.parse_next:			; cx = length, dx = start
	jcxz @FFF		; value range with length zero ?
@@:
	call .checkindex	; error if invalid/unused point
@@:
	inc dx			; next point
	loop @BB		; loop through value range
@@:
	dec si
	call skipcomma
	call iseol?
	jne .in			; more elements follow -->

	pop si			; restore -> start of list
	mov bp, .do_next	; set up handler to do, next
				; (fall through) and loop again

.in:
	dec si			; -> next nonblank
.notin:
	lodsb			; load first text byte
	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	call findpointat	; do we find it ?
	jc error		; not found -->
	mov cx, 1		; (for bb IN)
	jmp @F			; point index is in dx -->

.not_at:
	call near word [cs:bp - 2]
				; parse point indices
				;  (value range or single getpointindex)
	jnc @F			; point index is in dx, no keyword -->
.error_NZ:
	jnz error		; "NEW" is invalid -->

	call chkeol		; check that after "ALL" there is EOL
	xor cx, cx
.all_loop:
	mov ax, cx
	call calcpointbit	; get offset and mask
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jz .all_next		; skip unused point -->
	mov dx, cx		; dx = point index
	call di			; has to preserve cx, dx, di, si, bp
.all_next:
	inc cx
	cmp cx, _NUM_B_BP
	jb .all_loop		; do for all points
	retn			; done

@@:
	jmp bp			; handle dx = point, cx = amount
				;  with di = handler, si -> text

	align 2, db 0
	dw getpointindex
.checkend:
	call chkeol
	call .checkindex	; specified a valid point ?
	jmp di			; tailcall, dx = point index,
				;  bx, ah = offset and mask

		; INP:	dx = point index
		; OUT:	ah = mask
		;	bx = offset into bplist bit arrays
		;	branches to error on unused point
.checkindex:
	call .checkindex_internal
	jz error
	retn

		; INP:	dx = point index
		; OUT:	NZ if valid used point
		;	 ah = mask
		;	 bx = offset into bplist bit arrays
.checkindex_internal:
	mov ax, dx
	call calcpointbit	; bx, ah = offset and mask
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	retn


point_enable:
	not ax
%if ((_NUM_B_BP+7)>>3) != 1
	and byte [b_bplist.disabled_mask+bx], ah
%else
	and byte [b_bplist.disabled_mask], ah
%endif
	retn


point_disable:
%if ((_NUM_B_BP+7)>>3) != 1
	or byte [b_bplist.disabled_mask+bx], ah
%else
	or byte [b_bplist.disabled_mask], ah
%endif
	retn


point_toggle:
%if ((_NUM_B_BP+7)>>3) != 1
	xor byte [b_bplist.disabled_mask+bx], ah
%else
	xor byte [b_bplist.disabled_mask], ah
%endif
	retn


point_list:
	call iseol?
	je .all

	call getpointat		; "AT" keyword ?
	jc .not_at		; no -->
	call findpointat	; do we find it ?
		; Here we ignore the point index in dx, we just
		;  take note that at least one point matching the
		;  specified address exists. The points are matched
		;  against the linear address in ..@bb_saved_linear.
	jnc .all_matching

	mov dx, msg.bpnone_at
	call putsz
	retn


.all_matching:
	xor bp, bp
	xor bx, bx
	xor dx, dx
	mov di, line_out
.loop_matching:
	mov si, bx
	add si, si		; * 2
	add si, si		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add si, bx		; * 5
%elif BPSIZE == 6
	add si, bx		; * 5
	add si, bx		; * 6
%elif BPSIZE == 9
	add si, si		; * 8
	add si, bx		; * 9
%else
 %error Unexpected breakpoint size
%endif
	add si, b_bplist.bp	; -> bp structure
	lodsw
	cmp word [..@bb_saved_linear], ax
	jne .next_matching
%if _PM
	lodsw
%else
	xor ax, ax
	lodsb			; ax = high byte
%endif
	cmp word [..@bb_saved_linear + 2], ax
	jne .next_matching

	push di
	mov al, 32
	mov cx, 40
	rep stosb		; initialize field with blanks
	xor al, al
	stosb			; terminate it
	pop di

	call .single		; fill buffer

	push dx
	push bx
%if 0
	test dl, 1		; an odd point ?
	jnz .odd_matching	; yes -->
	mov di, line_out + 40	; write next point after the field
	jmp .was_even_matching
.odd_matching:
%endif
	call putsline_crlf	; put line with linebreak (and no excess blanks)
	call handle_bl_when
	mov di, line_out	; write next point at start of field
.was_even_matching:
	pop bx
	pop dx
	inc dx			; increment odd/even counter
.next_matching:
	inc bx			; increment breakpoint index
	cmp bx, _NUM_B_BP
	jne .loop_matching
	jmp .end


.not_at:
	call getpointindex
	jnc @F
	jnz error		; "NEW" is invalid -->

	call chkeol
	jmp .all
@@:
	call chkeol
	mov bx, dx
	mov di, line_out
	call .single
	call putsline_crlf
	jmp handle_bl_when


.all:
	xor bp, bp		; high byte: any set points encountered yet,
				; low byte: current line has any set points
	xor bx, bx
	mov di, line_out
.loop:
	push di
	mov al, 32
	mov cx, 40
	rep stosb		; initialize field with blanks
	xor al, al
	stosb			; terminate it
	pop di

	call .single		; fill buffer

	push bx
%if 0
	test bl, 1		; an odd point ?
	jnz .odd		; yes -->
	mov di, line_out + 40	; write next point after the field
	jmp .was_even
.odd:
%endif
	test bp, 00FFh		; any point set in this line ?
	jz .skip_putsline	; no -->
	call putsline_crlf	; put line with linebreak (and no excess blanks)
	call handle_bl_when

	and bp, ~00FFh		; clear flag for next line processing
.skip_putsline:
	mov di, line_out	; write next point at start of field
.was_even:
	pop bx
	inc bx
	cmp bx, _NUM_B_BP
	jne .loop
.end:
	cmp di, line_out
	je @F
	call putsline_crlf
	call handle_bl_when
@@:
	test bp, 0FF00h
	jnz @F
	mov dx, msg.bpnone
	call putsz
@@:
	retn

.single:
	mov si, msg.bp
	call showstring
	push bx
	mov ax, bx
	call hexbyte		; store index of this point
	call calcpointbit
	mov si, msg.bpunused
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask+bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif
	jnz @F			; if set -->
	call showstring
	xor si, si
	jmp .unused

@@:
	or bp, 0101h		; flag that there was a point set in this line
	mov si, msg.bpdisabled
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.disabled_mask+bx], ah
%else
	test byte [b_bplist.disabled_mask], ah
%endif
	jnz .disabled		; disabled --> (D)
	mov si, msg.bpenabled
.disabled:
	call showstring
	mov si, msg.bpaddress
	call showstring
	 pop ax
	 push ax
	mov si, ax
	add si, si		; * 2
	add si, si		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add si, ax		; * 5
%elif BPSIZE == 6
	add si, ax		; * 5
	add si, ax		; * 6
%elif BPSIZE == 9
	add si, si		; * 8
	add si, ax		; * 9
%else
 %error Unexpected breakpoint size
%endif
	add si, b_bplist.bp	; -> point
	push dx
	lodsw
	xchg ax, dx
%if _PM
	lodsw
	call hexword
%else
	xor ax, ax
	lodsb			; ax = high byte
	call hexbyte
%endif
	 push ax
	mov al, '_'
	stosb
	 pop ax
	xchg ax, dx
	call hexword		; display (linear) address
%if BPSIZE == 6 || BPSIZE == 9
		; INP:	dx:ax = linear address
		;	si -> (d)word offset
		;	di -> where to store
		; OUT:	cx = length displayed
		;	si -> after offset
		;	di -> after stored string
		; CHG:	ax, dx
	call bp_display_offset	; BPSIZE implied
%else
	xor cx, cx
%endif
	pop dx
	lodsb
	push ax
	mov si, msg.bpcontent
	call showstring
	pop ax
	call hexbyte		; display content
	mov si, msg.bpcounter
	call showstring
	 pop ax
	 push ax
	mov bx, ax
	push dx
	mov dx, ax
	add bx, bx		; * 2
	mov ax, word [b_bplist.counter + bx]
	call hexword

	mov bx, -1
	call get_set_id_offset_length
	test bh, 63 << 2	; length nonzero ?
	jz @F			; no -->

		; The maximum length of a short ID is based on
		;  how much space there is assuming 80 columns.
	mov si, msg.bb_hitpass_id.short
				; set up message for short ID
	shl cl, 1
	shl cl, 1		; length offset displayed << 2
				;  (bx >> 10 is ID length, ie bh >> 2)
	neg cl			; minus displayed
	add cl, 35 << 2		; 35 minus displayed
	cmp bh, cl		; long ?
	jb .trigger_short_id
		; This jump MUST be a jb, not jbe. The jbe
		;  would not match ZR for words where the
		;  idbuffer offset is a nonzero value.
	mov si, msg.bb_hitpass_id.long
				; use long ID message imstead
.trigger_short_id:

	call copy_single_counted_string
	mov cl, bh
	shr cl, 1
	shr cl, 1		; cx = length
	and bh, 1023 >> 8	; bx = offset
	lea si, [b_bplist.idbuffer + bx]
				; -> ID in buffer
	rep movsb		; copy ID

@@:

	mov bx, -1
	call get_set_when_offset
	mov si, bx
	pop dx

.unused:
	pop bx			; restore counter (if displaying all)
	retn


		; CHG:	si, al
handle_bl_when:
	xchg dx, si
	test dx, dx		; no WHEN condition ?
	jz @F			; yes -->
	push dx
	mov dx, msg.bb_when
	call putsz		; display label
	pop dx
	call putsz		; display condition
%if 0
	mov al, '$'
	call putc
%endif
	mov dx, crlf
	call putsz		; display trailer
@@:
	xchg dx, si
	retn


		; INP:	ax = 0-based index of point
		; OUT:	(bx-> byte to access. only if at least 9 points)
		;	(bx = 0 always if 8 or fewer points)
		;	ah = value to access
		; CHG:	al
calcpointbit:
%if ((_NUM_B_BP+7)>>3) != 1
	mov bx, ax		; index
%endif
	and al, 7		; 0 to 7
	mov ah, 1		; prepare mask
	xchg ax, cx		; cl = counter 0 to 7, ch = mask prepared
	shl ch, cl		; shift mask to access correct bit
%if ((_NUM_B_BP+7)>>3) != 1
	mov cl, 3
	shr bx, cl		; get offset
%else
	xor bx, bx		; offset = 0
%endif
	xchg ax, cx		; ah = mask
	retn


		; INP:	bx:dx = linear address
		; OUT:	NC if point found,
		;	 dx = point index
		;	CY if point not found,
		;	 bx:dx unchanged
		; CHG:	di
findpointat:
	lframe near
	lenter
	lvar word,	orig_ax
	 push ax
	lvar word,	orig_si
	 push si
	lvar dword,	orig_bxdx
	 push bx
	 push dx
	xor dx, dx
.loop:
	mov ax, dx
	call calcpointbit
%if ((_NUM_B_BP+7)>>3) != 1
	test byte [b_bplist.used_mask + bx], ah
%else
	test byte [b_bplist.used_mask], ah
%endif				; in use ?
	jz .next		; no -->

	mov si, dx
	add si, si		; * 2
	add si, si		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add si, dx		; * 5
%elif BPSIZE == 6
	add si, dx		; * 5
	add si, dx		; * 6
%elif BPSIZE == 9
	add si, si		; * 8
	add si, dx		; * 9
%else
 %error Unexpected breakpoint size
%endif
	add si, b_bplist.bp	; -> point
	lodsw
	cmp word [bp + ?orig_bxdx], ax
	jne .next
%if _PM
	lodsw
%else
	xor ax, ax
	lodsb			; ax = high byte
%endif
	cmp word [bp + ?orig_bxdx + 2], ax
	jne .next
				; (NC)
	pop bx			; discard dx on stack, clobbering bx
	jmp .ret_with_dx

.next:
	inc dx
	cmp dx, _NUM_B_BP
	jb .loop

	stc
.ret:
	pop dx
.ret_with_dx:
	pop bx			; pop ?orig_bxdx
	pop si			; pop ?orig_si
	pop ax			; pop ?orig_ax
	lleave
	lret


		; INP:	si->, al=
		; OUT:	CY if no "AT" keyword + address,
		;	 si, al unchanged
		;	NC if "AT" keyword + address,
		;	 si->, al= after
		;	 bx:dx = dword [..@bb_saved_linear] = linear address
		; CHG:	edx, bx
getpointat:
	dec si			; -> nonblank
	mov dx, msg.at		; "AT" keyword ?
	call isstring?
	lodsb			; load separator or nonblank
	je .at			; yes -->
	stc			; no
	retn

.at:
	mov bx, word [reg_cs]
	call getlinearaddr	; get address
	jc error
	mov word [..@bb_saved_linear], dx
	mov word [..@bb_saved_linear + 2], bx
				; also store this
	retn


		; INP:	si->, al=
		; OUT:	NC if a point is specified,
		;	 dx = point index (0-based, below _NUM_B_BP)
		;	CY if a keyword is specified,
		;	 ZR if "ALL" keyword specified
		;	 NZ if "NEW" keyword specified
getpointindex:
	dec si
	mov dx, msg.all		; "ALL" keyword ?
	call isstring?
	je .is_all		; yes --> (ZR)
	mov dx, msg.new		; "NEW" keyword ?
	call isstring?
	je .is_new		; yes -->
	lodsb
	nearcall getword	; get index
	cmp dx, _NUM_B_BP	; valid index ?
	jae error		; no -->
	clc			; (NC)
	retn

.is_new:
	test si, si		; (NZ) (si points into line_in)
.is_all:
	stc			; (CY)
	lodsb			; al = separator, si-> after
	retn
%endif


%if BPSIZE == 6 || BPSIZE == 9
		; INP:	dx:ax = linear address
		;	si -> (d)word offset
		;	di -> where to store
		; OUT:	cx = length displayed
		;	si -> after offset
		;	di -> after stored string
		; CHG:	ax, dx
bp_display_offset:
	lframe
	lvar dword,	offset
	lenter
	lvar dword,	linear
	 push dx
	 push ax
	mov ax, "  "
	lvar word,	prefix
	 push ax		; prefix: two blanks
	lvar word,	start_write
	 push di
	push bx
	lodsw
%if _PM
	xchg ax, dx
	lodsw
	cmp ax, -1		; high word is all-1s ?
	xchg ax, dx
	jne @F			; no -->
%else
	xor dx, dx
%endif
	cmp ax, -1		; all-1s ?
	je .skip		; yes -->
@@:
	mov word [bp + ?offset + 2], dx
	mov word [bp + ?offset], ax

	mov dx, word [bp + ?linear + 2]
	mov ax, word [bp + ?linear]
	sub ax, word [bp + ?offset]
	sbb dx, word [bp + ?offset + 2]
				; = base we want

%if _PM
	call ispm
	jnz .r86m

	push dx
	push ax			; stack = wanted base
	mov ax, 6
	mov bx, word [reg_cs]	; user CS
	int 31h			; cx:dx = CS base
	mov bx, dx		; cx:bx = base
	pop ax
	pop dx			; dx:ax = wanted base
	jc .try_r86m

	cmp cx, dx		; does it match CS ?
	jne .try_r86m
	cmp bx, ax
	jne .try_r86m		; no -->
				; yes
	mov ax, "  "
	stosw
	mov ax, "CS"
	stosw
	jmp .offset

.try_r86m:
	mov byte [bp + ?prefix + 1], '$'
				; change prefix to " $" to indicate 86M
%endif

.r86m:
	mov cx, 4
	test al, 15		; on a paragraph boundary ?
	jnz .questionmarks	; no -->
	test dx, 0FFF0h		; segment base <= 0F_FFF0h ?
	jnz .questionmarks	; no -->
	shr ax, cl		; = low 12 bits of base segment
	ror dx, cl		; move high 4 bits of base segment up
	or dx, ax		; = base
	mov ax, word [bp + ?prefix]
	stosw			; store the prefix
	xchg ax, dx
	call hexword		; write address

.offset:
	mov al, ':'
	stosb			; store a colon
%if _PM
	mov ax, word [bp + ?offset + 2]
	test ax, ax
	jz @F
	call hexword		; nonzero upper word
@@:
%endif
	mov ax, word [bp + ?offset]
	call hexword		; lower word

.skip:
	pop bx
	pop cx			; get ?start_write
	neg cx			; minus start offset
	add cx, di		; end offset minus start offset = length
	lleave
	retn

.questionmarks:
	mov ax, "  "
	stosw
	mov ax, "??"
	stosw
	stosw			; fill with question marks
	jmp .offset


%if _BREAKPOINTS
point_swap:
	call getpointindex
	jc .error
	mov bp, dx		; bp = first index
	call getpointindex	; dx = second index
	jc .error
	call chkeol

	mov cx, 2		; loop across pushing twice
.loop_push:
	mov ax, dx
	call calcpointbit
	mov al, ah		; al = ah = mask
	and ah, byte [b_bplist.used_mask + bx]
	and al, byte [b_bplist.disabled_mask + bx]
				; ah = nonzero if used, al = nonzero if disabled
	push ax			; push flag bits word
	mov bx, dx
	add bx, bx		; * 2
	push word [b_bplist.counter + bx]
	push word [b_bplist.id + bx]
	push word [b_bplist.when + bx]
				; push array entries

	add bx, bx		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add bx, dx		; * 5
%elif BPSIZE == 6
	add bx, dx		; * 5
	add bx, dx		; * 6
%elif BPSIZE == 9
	add bx, bx		; * 8
	add bx, dx		; * 9
%else
 %error Unexpected breakpoint size
%endif
	push word [b_bplist.bp + bx]
	push word [b_bplist.bp + bx + 2]
%if BPSIZE == 4
%elif BPSIZE == 5
	push word [b_bplist.bp + bx + 4]
%elif BPSIZE == 6
	push word [b_bplist.bp + bx + 4]
%elif BPSIZE == 9
	push word [b_bplist.bp + bx + 4]
	push word [b_bplist.bp + bx + 6]
	push word [b_bplist.bp + bx + 8]
%else
 %error Unexpected breakpoint size
%endif				; push point
	xchg bp, dx		; swap to the other point
	loop .loop_push		; iterate for two times pushing

	mov cl, 2		; loop across popping twice
.loop_pop:
	mov bx, dx
	add bx, bx		; * 2
	add bx, bx		; * 4
%if BPSIZE == 4
%elif BPSIZE == 5
	add bx, dx		; * 5
%elif BPSIZE == 6
	add bx, dx		; * 5
	add bx, dx		; * 6
%elif BPSIZE == 9
	add bx, bx		; * 8
	add bx, dx		; * 9
%else
 %error Unexpected breakpoint size
%endif
%if BPSIZE == 4
%elif BPSIZE == 5
	pop ax
	mov byte [b_bplist.bp + bx + 4], al
%elif BPSIZE == 6
	pop word [b_bplist.bp + bx + 4]
%elif BPSIZE == 9
	pop ax
	mov byte [b_bplist.bp + bx + 8], al
	pop word [b_bplist.bp + bx + 6]
	pop word [b_bplist.bp + bx + 4]
%else
 %error Unexpected breakpoint size
%endif
	pop word [b_bplist.bp + bx + 2]
	pop word [b_bplist.bp + bx]
				; pop point

	mov bx, dx
	add bx, bx		; * 2
	pop word [b_bplist.when + bx]
	pop word [b_bplist.id + bx]
	pop word [b_bplist.counter + bx]
				; pop array entries
	pop si
	mov ax, dx
	call calcpointbit
	xchg si, dx
	not ah			; mask to turn off flag bits
	and byte [b_bplist.used_mask + bx], ah
	and byte [b_bplist.disabled_mask + bx], ah
				; clear bits
	not ah			; get back mask to turn on
	test dl, dl		; nonzero if disabled
	jz @F			; not disabled -->
	or byte [b_bplist.disabled_mask + bx], ah
@@:
	test dh, dh		; nonzero if used
	jz @F			; not used -->
	or byte [b_bplist.used_mask + bx], ah
@@:
	xchg si, dx		; restore dx = point index
	xchg bp, dx		; swap to the other point
	loop .loop_pop
	retn

.error:
	jmp error
%endif
%endif

%if _DUALCODE
	usesection lDEBUG_CODE2

section_of bu_relocated
dualfunction
bu_relocated: section_of_function
	lframe dualdistance
	lpar word, sign
	lenter
	mov ax, word [bp + ?sign]
	mov di, msg.bu_relocated.sign
	nearcall hexword
	mov dx, msg.bu_relocated
	nearcall putsz
	lleave
	dualreturn
	lret

	usesection lDEBUG_CODE
%endif

bu_breakpoint:
%if _DUALCODE
	cmp al, '2'
	je .2
%endif
	call chkeol
%if _DEBUG || _LOADER
 %if _DEBUG_COND
	testopt [internalflags6], dif6_debug_mode
	jnz @F
	mov ax, 013Ah
	call setrc
	mov dx, msg.bu_disabled
	jmp putsz
@@:
 %endif
	mov dx, msg.bu
	call putsz
	pop dx			; discard near return address
	mov dx, dmycmd		; point dx to empty function
	jmp cmd4.int3		; run a breakpoint right before dispatcher
%else
	mov ax, 013Bh
	call setrc
	mov dx, msg.notbu
	jmp putsz
%endif

%if _DUALCODE
.2:
	call skipwhite
	call chkeol
	mov ax, 2642h
	 push ax
	dualcall bu_relocated
	retn
%endif
