; UTF8COUNT.ASM v0.02
; Copyright (C) 2021-2022 Boo Khan Ming
;
; Derived from HEXDUMP.ASM
;
;
; v0.01 - initial release
; v0.02 - show decimal numbers of characters count (instead of just hexadecimal number)
;       - show total characters count (byte1+byte2+byte3+byte4)
;
;


format ELF64 executable 3

segment readable executable

entry $

      pop     r8
      pop     rsi	;APP_NAME
      pop     rsi	;1st command-line argument
      mov     rdi,rsi
      cmp     rdi,0
      je      _err   
      ;lea     rdi,[fn]
      xor     rsi,rsi   ;O_RDONLY
      mov     rax,2     ; sys_create
      syscall
      cmp     rax,-1
      je      _err
      mov     dword [fd],eax

_redo:
      mov     rdx,16
      lea     rsi,[buffer]      
      mov     edi,dword [fd]
      xor     rax,rax  ; sys_read
      syscall
      cmp     rax,0
      je      _close
      mov     [count],eax

      mov     edx,[offset]
      mov     ecx,8
      call    ConvertHex
      call    PrintOffset
      call    PrintLongSpace      
      mov     rcx,0
      
_repeat1:      
      xor     rdx,rdx
      mov     dl,byte [buffer + rcx]
      push    rcx
      push    rdx
      mov     rcx,2
      call    ConvertHex2
      pop     rdx
      call    PrintHex
      call    PrintShortSpace
      pop     rcx
      inc     rcx 
      cmp     ecx,[count]
      jb      _repeat1
      cmp     ecx,16
      jb      _pad1    
      jmp     _skip1
_pad1:
      mov     ebx,16
      sub     ebx,ecx
_pad3:
      call    PrintShortSpace
      call    PrintShortSpace
      call    PrintShortSpace
      dec     ebx
      jnz     _pad3   
                    
_skip1:      
      call    PrintLongSpace
      mov     rcx,0
      
_repeat2:      
      xor     rdx,rdx
      mov     dl,byte [buffer + rcx]
      push    rcx
      cmp     dl,32
      jb      _dot
      cmp     dl,127
      jae     _dot
      mov     [dummy],dl
      call    PrintChar
      jmp     _skip2
 
 _dot:
      mov     [dummy],'.'
      call    PrintChar
 _skip2:        
      pop     rcx
      inc     rcx 
      cmp     ecx,[count]
      jb      _repeat2
      cmp     ecx,16
      jb      _pad2    
      jmp     _skip3
_pad2:
      mov     ebx,16
      sub     ebx,ecx
_pad4:      
      call    PrintShortSpace
      call    PrintShortSpace
      dec     ebx
      jnz     _pad4
            
_skip3:
      call    PrintLine      
      add     [offset],16
      jmp     _redo
      
_close:            
      mov     edi,dword [fd]
      mov     rax,3    ; sys_close
      syscall
      mov     rdx, lenyellow
      lea     rsi, [coloryellow]
      call    Print
      mov     rdx,strlen1
      lea     rsi,[str1]
      call    Print
      mov     rdx, lenblack
      lea     rsi, [colorblack]
      call    Print 
      mov     edx,[utf8_1]
      mov     ecx,8
      call    ConvertHex
      call    PrintOffset
      
      call    PrintLongSpace
      mov     esi, [utf8_1]
      call    bin2ascii
      mov     esi, result
      mov     rdx, 16
      call    Print 
      call    PrintLine  
      
      mov     rdx, lenorange
      lea     rsi, [colororange]
      call    Print   
      mov     rdx,strlen2
      lea     rsi,[str2]
      call    Print      
      mov     rdx, lenblack
      lea     rsi, [colorblack]
      call    Print 
      mov     edx,[utf8_2]
      mov     ecx,8
      call    ConvertHex
      call    PrintOffset
      
      call    PrintLongSpace
      mov     esi,[utf8_2]
      call    bin2ascii
      mov     esi, result
      mov     rdx, 16
      call    Print 
      call    PrintLine  
      
      mov     rdx, lengreen
      lea     rsi, [colorgreen]
      call    Print  
      mov     rdx,strlen3
      lea     rsi,[str3]
      call    Print       
      mov     rdx, lenblack
      lea     rsi, [colorblack]
      call    Print 
      mov     edx,[utf8_3]
      mov     ecx,8
      call    ConvertHex
      call    PrintOffset
      
      call    PrintLongSpace
      mov     esi,[utf8_3]
      call    bin2ascii
      mov     esi, result
      mov     rdx, 16
      call    Print 
      call    PrintLine  
      
      mov     rdx, lenblue
      lea     rsi, [colorblue]
      call    Print
      mov     rdx,strlen4
      lea     rsi,[str4]
      call    Print
      mov     rdx, lenblack
      lea     rsi, [colorblack]
      call    Print   
      mov     edx,[utf8_4]
      mov     ecx,8
      call    ConvertHex
      call    PrintOffset  
      
      call    PrintLongSpace
      mov     esi,[utf8_4]
      call    bin2ascii
      mov     esi, result
      mov     rdx, 16
      call    Print 
      call    PrintLine      
      
      mov     esi,[utf8_1]
      add     esi,[utf8_2]
      add     esi,[utf8_3]
      add     esi,[utf8_4]
      call    bin2ascii
      mov     rdx, 16
      mov     esi, result
      call    Print 
      call    PrintLine

_err:
      mov     rdi,rax
      mov     rax,60   ; sys_exit
      syscall

PrintLongSpace:
      mov     rdx,len1
      lea     rsi,[space1]
      call    Print
      ret
PrintShortSpace:
      mov     rdx,len2
      lea     rsi,[space2]
      call    Print
      ret
PrintLine:
      mov     rdx,1
      mov     [dummy],10
      lea     rsi,[dummy]
      call    Print
      ret
PrintOffset:
      mov     rdx,8
      lea     rsi,[hexnum]
      call    Print
      ret      
PrintHex:
      cmp     dl, 011110000b
      jae     _4byte
      cmp    dl, 011100000b
      jae      _3byte
      cmp    dl, 011000000b
      jae      _2byte
      cmp     dl, 001111111b
      jbe     _1byte
      
      ;the others are continuation byte, don't count
      mov     rdx, lenblack
      lea     rsi, [colorblack]
      call    Print    
      jmp     next
      
_4byte:
      mov     rdx, lenblue
      lea     rsi, [colorblue]
      call    Print
      inc     dword [utf8_4]
      jmp     next
      
_3byte:
      mov     rdx, lengreen
      lea     rsi, [colorgreen]
      call    Print
      inc     dword [utf8_3]
      jmp     next      
      
_2byte:
      mov     rdx, lenorange
      lea     rsi, [colororange]
      call    Print
      inc     dword [utf8_2]
      jmp     next
      
_1byte:
      mov     rdx, lenyellow
      lea     rsi, [coloryellow]
      call    Print
      inc     dword [utf8_1]
      
next:
      mov     rdx,2
      lea     rsi,[hexval]
      call    Print
      
      mov     rdx, lenblack
      lea     rsi, [colorblack]
      call    Print      
      ret    
PrintChar:      
      mov     rdx,1
      lea     rsi,[dummy]
      call    Print
      ret
Print:      
      ;mov     rdx,rax
      ;lea     rsi,[buffer]
      mov     rdi,1    ; STDOUT
      mov     rax,1    ; sys_write
      syscall      
      ret
            
ConvertHex:                                     ;-) Nice code snippet by Tomasz Grysztar (flat assembler)
      ;mov      ecx,8
      xor      ebx,ebx
_loop1:
      rol      edx,4
      mov      eax,edx
      and      eax,1111b
      mov      al,[digits+eax]
      mov      [ebx+hexnum],al
      inc      ebx
      dec      ecx
      jnz      _loop1     
      ret 
      
ConvertHex2:                                     ;-) Nice code snippet by Tomasz Grysztar (flat assembler)
      ;mov      ecx,8
      xor      ebx,ebx
_loop2:
      rol      dl,4
      mov      al,dl
      and      eax,1111b
      mov      al,[digits+eax]
      mov      [ebx+hexval],al
      inc      ebx
      dec      ecx
      jnz      _loop2     
      ret       
      
magic1  equ     0a7c5ac47h
magic2  equ     068db8badh

bin2ascii:   ;-) Nice code snippet optimized by El Tangas from AMD Athlon™ Processor x86 Code Optimization Guide" page 139

;On entry: esi - number to be converted, edi - destination of ASCII result
	mov	eax,magic1
	mul	esi

;Turns out this has not enough precision, because many digits of the fraction
;0.0000a7c5ac471b478423 were ignored. I verified that it is enough to had just
;another precision nibble, rounding to 0.0000a7c5ac472
;multiply by 0.20000000h = divide by 8 = shr by 3
	mov	ecx,esi
	shr	ecx,3

;sum the two partial terms, obtaining the final edx:eax = qqqq.rrrr:rrrrrrrr
	add	eax,ecx
	adc	edx,0

;now we separate the quotient from the remainder: 
;qqqq.rrrr:rrrrrrrr -> qqqq.0000h, 0.rrrrrrrh (have to keep track of the hexadecimal point)
	shrd	eax,edx,20		;separate remainder
	and	edx,0FFFF0000h		;mask quotient
	inc	eax			;we loose 4 significand remainder nibbles, round up
	and	eax,0FFFFFFFh		;remove quotient nibble from remainder.
	push	rax			;store remainder

;Now we can process the quotient to obtain the five high decimal digits.
;We have qqqq.0000h in edx, and we know that qqqqh <= 42949d, so if we divide by 10000d
;using the multiply by reciprocal method, we obtain q.rrrrrrrh in edx. To do this, we
;multiply by 0.000068db8badh
	mov	eax,magic2
	mul	edx
	inc	edx			;round up

;We already have the first decimal digit isolated in the high nibble of edx, so now it can
;be stored, and masked out to keep the remainder.
	mov	eax,edx
	shr	edx,28
	and	eax,0FFFFFFFh
	add	dl,'0'
	lea edi,[result] ;<--added
	mov	[edi],dl

;The rest of the digits are extracted from the remainder by successive multiplies by 10d,
;masking the remainder for the next step.
	mov	ecx,4
prox_dig_hi:
	lea	eax,[4*eax+eax]		;multiply by 5
	inc	edi
	add	eax,eax			;multiply by 2
	mov	edx,eax
	shr	edx,28
	and	eax,0FFFFFFFh
	add	dl,'0'
	dec	ecx
	mov	[edi],dl
	jnz	prox_dig_hi

;Recover the lower digits and repeat the same process.
	pop	rax
	mov	ecx,5
prox_dig_lo:
	lea	eax,[4*eax+eax]
	inc	edi
	add	eax,eax
	mov	edx,eax
	shr	edx,28
	and	eax,0FFFFFFFh
	add	dl,'0'
	dec	ecx
	mov	[edi],dl
	jnz	prox_dig_lo
    ;mov     byte [edi+10],0        
    ret      
 
segment readable writeable

result rb      10
       db      ' chars',0
buffer rb      16
fd     dd      ?
count  dd      ?
offset dd      0
;fn     db      'cpubrand.asm',0
hexnum rb      8
hexval rb      2
digits db      '0123456789ABCDEF' 
space1 db      32,32
len1   = $ - space1
space2 db      32
len2   = $ - space2
dummy  db      ?
utf8_1  dd     0
utf8_2  dd     0
utf8_3  dd     0
utf8_4  dd     0
str1    db   '1-byte UTF-8: '
strlen1 = $ - str1
str2    db   '2-byte UTF-8: '
strlen2 = $ - str2
str3    db   '3-byte UTF-8: '
strlen3 = $ - str3
str4    db   '4-byte UTF-8: '
strlen4 = $ - str4
colorblue   db   27,'[48:5:33m'   ; ANSI escape code
lenblue = $ - colorblue
colorgreen   db   27,'[48:5:46m'   ; ANSI escape code
lengreen = $ - colorgreen
colororange   db   27,'[48:5:196m'   ; ANSI escape code
lenorange = $ - colororange
coloryellow   db   27,'[48:5:172m'   ; ANSI escape code
lenyellow = $ - coloryellow
colorblack   db   27,'[48:5:0m'   ; ANSI escape code
lenblack = $ - colorblack

