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
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