Key-scanning macro in NASM Win16 against DOSBOX (Intel 8086) Supports backspacing, but not wrapped backspacing.


%macro newline 0.nolist
    mov     ax, 0E0Dh
    int     10h
    mov     al, 0Ah
    int     10h
%endmacro

%macro getc 0.nolist
%%get:
    mov     ah, 11h
    int     16h        ; get keystroke status
    jnz     %%pressed
    hlt
    jmp     %%get
%%pressed:
    mov     ah, 10h
    int     16h        ; get the pressed key on "modern" keyboards
%endmacro

; %1 = string to write into
%macro scan 1
    pushaw
    mov     di, %1     ; prep for stosb
    xor     cx, cx
%%get:
    getc
    cmp     al, 13
    je      %%done     ; enter pressed
    cmp     al, 8
    je      %%bspace
    cmp     al, ' '
    jb      %%get      ; ignore most non-printing characters
    cmp     al, '~'
    ja      %%get

    push    bp
    mov     ah, 0Eh    ; print the valid character
    int     10h
    push    bp

    stosb              ; write the character to the string
    inc     cx
    cmp     cx, 80     ; ensure the buffer isn't exceeded
    jae     near %%done
    jmp     near %%get ; room for more
%%bspace:
    cmp     cx, 0
    je      %%get      ; ignore starting bspace

    push    bp
    mov     ax, 0E08h
    int     10h        ; bspace twice, to clear space
    mov     al, 32
    int     10h
    mov     al, 8
    int     10h
    pop     bp

    dec     di         ; overwrite character position
    dec     cx         ; step back a character
    jmp     %%get
%%done:
    xor     ax, ax
    stosb              ; append null-terminating character (zero)
    newline
    popaw
%endmacro
It's fine to write this scan macro for educational purposes, but I think that considering its length a subroutine would have been more appropriate. The macro's bytes are inserted everywhere the macro is invoked. That adds up! Having that many duplicated bytes in a program is a waste of space.

Some observations


push    bp
mov     ah, 0Eh    ; print the valid character
int     10h
push    bp
stosb              ; write the character to the string



That second push bp was probably meant to be pop bp, wasn't it?
But why would you want to preserve the BP register when the BIOS.Teletype function doesn't even touch it.  

User @ecm commented about the BIOS Teletype function: BUG: If the write causes the screen to scroll, BP is destroyed by BIOSes for which AH=06h destroys BP
If this is the bug that made you decide to preserve the BP register, then I would say that preserving BP is still not the right thing to do. Because your code only supports non wrapped backspacing, it is reasonable to assume that no scrolling will occur. If scrolling were to happen then the backspacing user could get confused! Furthermore it's easy to make sure that scrolling can't occur simply by setting the cursor at the left edge of the screen at the start.

For maximum safety, I would write the char from AL in the string before teletyping it. On some implementations of BIOS/DOS etc. , I've seen the AX register modified even when the official documentation told it was preserved.

stosb              ; write the character to the string
mov     bh, 0      ; DisplayPage
mov     ah, 0Eh    ; print the valid character
int     10h


I've added the required BIOS parameter for the DisplayPage. In all of your posts you keep ignoring this parameter. I'm wondering if you have any documentation about these BIOS video functions that possitively states that there are no selectable display pages.




%%bspace:
    cmp     cx, 0
    je      %%get      ; ignore starting bspace



The cmp cx, 0 instruction has a 3-byte encoding, but the test cx, cx instruction (that does the same) has but a 2-byte encoding. In any macro you will want to write the shortest code.

%%bspace:
    test    cx, cx
    jz      %%get      ; ignore starting bspace


There's also the jcxz instruction that you could use here. Again shorter but some people will say it is slow. It's up to you to decide if that is of real importance here. Don't forget that the user at the keyboard is much, much slower than any of the lesser instructions that you could choose!

%%bspace:
    jcxz    %%get      ; ignore starting bspace





cmp     cx, 80     ; ensure the buffer isn't exceeded
jae     near %%done
jmp     near %%get ; room for more



The code becomes a little more efficient if you write the jump that is more likely first. The buffer will nearly never fill up completely, and so the jump to %%done will almost never happen.

cmp     cx, 80     ; ensure the buffer isn't exceeded
jb      near %%get ; room for more
jmp     near %%done


A quick rewrite

; %1 = string to write into
%macro scan 1
    pushaw
    mov     di, %1     ; prep for stosb
    xor     cx, cx
    mov     bh, 0      ; DisplayPage
%%get:
    getc
    cmp     al, 13
    je      %%done     ; enter pressed
    cmp     al, 8
    je      %%bspace
    cmp     al, ' '
    jb      %%get      ; ignore most non-printing characters
    cmp     al, '~'
    ja      %%get

    stosb              ; write the character to the string
    mov     ah, 0Eh    ; print the valid character
    int     10h
    inc     cx
    cmp     cx, 80     ; ensure the buffer isn't exceeded
    jb      near %%get ; room for more
    jmp     near %%done
%%bspace:
    jcxz    %%get      ; ignore starting bspace
    mov     ax, 0E08h
    int     10h        ; bspace twice, to clear space
    mov     al, 32
    int     10h
    mov     al, 8
    int     10h
    dec     di         ; overwrite character position
    dec     cx         ; step back a character
    jmp     %%get

%%done:
    xor     ax, ax
    stosb              ; append null-terminating character (zero)
    newline
    popaw
%endmacro