As my applications rely on VT-100 emulation, I found that I had to do quite a few workarounds to determine the number of visible characters in a string with embedded VT-100 control codes. So that led to the design of this 73 byte snippet.
Function would be called as follows;
xor edx, edx ; ARG1 - DX = 0 Search for first NULL.
mov ecx, edx ; ARG1 - Not more than ECX chars.
mov esi, SignOn ; ARG0 - Pointer to ASCII string.
call FindChr ; Length of string returned in RAX.
with text @ 0x40017c
SignOn db 27, '[2J', 27, '[1;37m', 27, '[2;000H'
db 'Horizons Buisness ', 27, '[42m& Personal Finance'
db 27, '[40;39m', 0
which returns
RAX = 0x24 = 36
RCX = 0xffffffbc = negated (0x44 = 68)
RSI = 0x4001c0
Assembled using NASM 2.13.02
; =============================================================================
; Search for character defined in DL but not past one in DH for a maximum
; count of ECX. Ignore VT-100 control codes but there are considered in
; total buffer size specified in RCX.
; ENTER: RSI = Pointer to ASCII string.
; ECX = Maximum interations (if 0 then 4,294,967,295).
; DL = unsigned char to match.
; DH = not beyond this character (terminator).
; LEAVE: EAX = Total printable characters.
; RSI = Points to match char in DL or RSI+1 if DH.
; = If SF (overflow) end of buffer + 1.
; ECX = Bytes remaining
; = If SF -1 (0xffffffff)
; DX = Unchanged.
; Total length of string can be derived from either
; negating ECX if ECX was zero on entry
; or ECX - original ECX - 1
; FLAGS: ZF = Match found (DL)
; NZ = Match found (DH)
; JS = ECX exhausted (overflow)
ESC equ 27 ; 0x1b o033 11011b
; -------------------------------------------------------------------------
FindChr:
push rbx
xor ebx, ebx ; Set initial character count.
; As this is essentially a do -> while (maxCount >=0) if there is a
; non-zero value in ECX it must be zero indexed so the proper number of
; of characters are evaluated.
test ecx, ecx
jz $ + 4
dec ecx ; Zero index max buffer count.
.L0: mov al, [rsi]
cmp al, ESC ; Test for beginning of VT-100 code first.
jnz .J0
; ----------
; If the next character is left bracket, then it is a VT-100 code.
cmp byte [rsi+1], '['
jnz .J0 ; NZ = Consider ESC a printable char
.l0: dec ecx
jz .done - 2
lodsb
cmp al, 'A'
jae .j0
; There are two codes that are not terminated with an alphabetic char
; that are used to select fonts.
cmp al, '('
jz .L0
cmp al, ')'
jnz .l0 ; Must be still inside control code
; This works as we are only concered with alphabetic chars.
.j0: and al, 0x5f ; Convert to upper case
cmp al, 'Z'
jbe .L0 ; CY or ZF we are at end of VT-100 code.
jmp .l0 ; Continue scanning inside VT-100 code
; ----------
.J0: cmp al, dl ; Is char we are looking for?
jz .done ; ZF = Stay at char just read and exit
inc rsi ; Bump pointer to next character
cmp al, dh ; Is it delimiter
jnz .J1
test esi, esi ; So NZ is set.
jmp .done ; Return with SF.
.J1: inc ebx ; Bump count of printable characters
dec ecx ; and maximum buffer count
jnz .L0
dec ecx ; Sets sign flag
.done:
mov eax, ebx ; Return char count in RAX
pop rbx
ret
NOTE The need for pointer to first printable character arose, so I've made this amendment.
.J1: inc ebx ; Bump count of printable characters
dec ecx ; and maximum buffer count
jz .done - 2
; This will set a pointer to first printable character in string
test rdi, rdi
jnz .L0
; Update pointer to address just before current RSI
mov rdi, rsi
dec rdi
jmp .L0