Files
klibc/src/convert.asm
2025-07-20 11:33:07 +02:00

450 lines
8.3 KiB
NASM

%include "src/constants.asm"
extern clamp
section .bss
cnvtBuff resb 67
cnvtBuffRev resb 67
section .text
global atoi
global itoa
global utoa
;----- atoi(*str[]) -----;
; Converts numeric string to an integer
; Return value: Converted integer or 0 if input was invalid
; Used registers:
; rax* (ret)
; rdi* (arg) Numeric string to convert
; rsi* Stores base
; rdx* Stores max usable ascii number
; rcx* Loop counter
; r8* tmp storage rsi (cmov) >> Loop counter for calculating base^n
; r9* tmp storage rdx (cmov) >> Calculated base^n
; r10* Stores return value (since RAX is used by IMUL)
atoi:
xor rax, rax
xor rcx, rcx
xor r10, r10
cmp byte [rdi], EOS
je .quit
; -- Test if negative (increase pointer to str, result is always calculated positively. Once reaching '-' again, then NEG)
cmp byte [rdi], '-'
jne .noMinusSign
inc rdi
inc rcx ;make sure .calculateLoop includes minus sign
; -- Determine base
.noMinusSign:
; hex?
mov r8, 16
cmp word [rdi], '0x'
cmove rsi, r8
je .skipBaseNotation
; octal?
mov r8, 8
mov r9, '7'
cmp word [rdi], '0o'
cmove rsi, r8
cmove rdx, r9
je .skipBaseNotation
; binary?
mov r8, 2
mov r9, '1'
cmp word [rdi], '0b'
cmove rsi, r8
cmove rdx, r9
je .skipBaseNotation
; nope, decimal
mov rsi, 10
mov rdx, '9'
jmp .getLen
.skipBaseNotation:
cmp rcx, 1 ;If '-' was found, using base is invalid
je .quit
add rdi, 2
; -- Get length of numeric string + test if valid
.getLen:
.getLenLoop:
cmp byte [rdi], EOS
je .calculate
cmp byte [rdi], '0'
jb .quit
; Test if valid
cmp rsi, 16
je .getLenLoop_testHex
cmp byte [rdi], dl
ja .quit
jmp .getLenLoop_cnt
.getLenLoop_testHex:
cmp byte [rdi], 'F'
ja .quit
cmp byte [rdi], 'A'
jb .testHex_isNum
jmp .getLenLoop_cnt
.testHex_isNum:
cmp byte [rdi], '9'
ja .quit
.getLenLoop_cnt:
inc rdi
inc rcx
jmp .getLenLoop
; -- Calculate the number
.calculate:
dec rdi ;don't point to EOS
xor r8, r8
.calculateLoop:
cmp byte [rdi], '-'
je .wrapup_neg
; Calculate base^n
.calcBase:
test r8, r8
jz .calcBase_firstLoop
cmp r8, 1
je .calcBase_secondLoop
jmp .calcBase_otherLoops
.calcBase_firstLoop:
mov r9, 1
jmp .calcNum
.calcBase_secondLoop:
mov r9, rsi
jmp .calcNum
.calcBase_otherLoops:
mov rax, rsi
mov r9, r8
dec r9
.calcBaseLoop:
imul rsi
dec r9
test r9, r9
jnz .calcBaseLoop
mov r9, rax
; Calculate number
.calcNum:
xor rax, rax
mov al, byte [rdi]
cmp al, 'A'
jb .calcNum_notHex
sub al, 55 ;'A'-10 (65-10)
jmp .calcNum_cnt
.calcNum_notHex:
sub al, '0'
.calcNum_cnt:
imul r9
add r10, rax
dec rdi
inc r8
loop .calculateLoop
jmp .quit
.wrapup_neg:
neg r10
.quit:
mov rax, r10
ret
;----- itoa(int, base, padLen, bool padZeroes) -----;
; Converts a signed integer to a string
; Return value: Pointer to converted string or 0(EOS) if entered base is invalid
; Supported bases: 10 (decimal)
; Used registers:
; rax* num to divide (DIV) >> Copy bytes when reversing string >> (ret) Pointer to converted string
; rdi* (arg) Integer to convert
; rsi* (arg) Base to convert to
; rdx* (arg) Padding length >> modulo (DIV)
; rcx* (arg) 0=pad spaces, otherwise pad zeroes >> Loop counter
; r9* Counts string length
; r10* Points to cnvtBuff(Rev)[]
; r11* Remembers if number is negative (r11b)
; r12 Stores (int)
; r13 Stores (base)
; r14 Stores (padLen)
; r15 Stores (padZeroes)
itoa:
; Before doing anything, check if base is valid
cmp rsi, 10
je .ok
cmp rsi, ~10
je .negBase
xor rax, rax
ret
.negBase:
not rsi
.ok:
push r12
push r13
push r14
push r15
; Store original arguments
mov r12, rdi
mov r13, rsi
mov r14, rdx
mov r15, rcx
; First, assure 0 <= padLen <= 64 ;(64, even though max signed int64 = 19 characters; enables to pad with base 2)
mov rdi, rdx
xor rsi, rsi
mov rdx, 64
call clamp
mov r14, rax
; Store padding character
mov rdi, ' '
mov rdx, '0'
test r15, r15
cmovnz rdi, rdx
mov r15, rdi
; Prepare for conversion
mov rax, r12
xor r9, r9
lea r10, [rel cnvtBuffRev]
; Check if negative
xor r11b, r11b
test rax, rax
jns .convert
mov r11b, 1
neg rax
.convert:
xor rdx, rdx
div r13
add rdx, '0'
mov byte [r10], dl
inc r10
inc r9
test rax, rax
jnz .convert
; Adds '-' if needed
cmp r15, '0'
je .checkPadding ; If padding with zeroes, add minus sign before zeroes. Otherwise, add '-' and then add spaces
test r11b, r11b
jz .checkPadding
mov byte [r10], '-'
inc r10
inc r9
.checkPadding:
; Substract length of converted number from padLen
sub r14, r9
cmp r14, 0
jle .makeString
mov rcx, r14
.addPadding:
mov byte [r10], r15b
inc r10
inc r9
loop .addPadding
cmp r15, ' '
je .makeString
; Adds '-' if needed
test r11b, r11b
jz .makeString
mov byte [r10], '-'
inc r10
inc r9
.makeString:
dec r10 ;don't point to past last character
lea rdi, [rel cnvtBuff]
mov rcx, r9
xor rax, rax
.makeStringLoop:
mov al, byte [r10]
mov byte [rdi], al
inc rdi
dec r10
loop .makeStringLoop
mov byte [rdi], EOS
pop r15
pop r14
pop r13
pop r12
lea rax, [rel cnvtBuff]
ret
;----- utoa(int, base, padLen, bool padZeroes, bool upperCase) -----;
; Converts an unsigned integer to a string
; Return value: Pointer to converted string or 0(EOS) if entered base is invalid.
; <!> To use prefix (ie 0x, 0b or 0o), pass ~base instead of base
; Supported bases: 2 (binary), 8, (octal), 10 (decimal), 16 (hexadecimal)
; Used registers:
; rax* num to divide (DIV) >> Copy bytes when reversing string >> (ret) Pointer to converted string
; rbx Stores (upperCase)
; rdi* (arg) Integer to convert >> base (positive) >> pointer to cnvtBuff[]
; *rsi (arg) Base to convert to
; *rdx (arg) Padding length >> modulo (DIV)
; *rcx (arg) 0=pad spaces, otherwise pad zeroes >> Loop counter
; *r8 (arg) 0=lowercase letters (for bases using letters, ie 16), uppercase otherwise >> Stores padding character >> used for inserting prefix
; r9* Counts string length
; r10* Points to cnvtBuff(Rev)[]
; *r11 Stores padZeroes
; r12 Stores (int)
; r13 Stores (base)
; r14 Stores (padLen)
; r15 Stores (padZeroes)
utoa:
%macro insert_prefix 0
xor r8, r8
xor rsi, rsi
mov r8, 'x0'
cmp rdi, 16
cmove rsi, r8
je %%insert
mov r8, 'o0'
cmp rdi, 8
cmove rsi, r8
je %%insert
cmp rdi, 2
jne %%continue
mov rsi, 'b0'
%%insert:
mov word [r10], si
add r10, 2
add r9, 2
%%continue:
;nop
%endmacro
;;
;; ENTRY
;;
; Before doing anything, check if base is valid
cmp rsi, 2
je .ok
cmp rsi, 8
je .ok
cmp rsi, 10
je .ok
cmp rsi, 16
je .ok
cmp rsi, ~16
je .ok
cmp rsi, ~10
je .negBase
cmp rsi, ~8
je .ok
cmp rsi, ~2
je .ok
xor rax, rax
ret
.negBase:
not rsi
.ok:
push rbx
push r12
push r13
push r14
push r15
; Store original arguments
mov rbx, r8
mov r12, rdi
mov r13, rsi
mov r14, rdx
mov r15, rcx
; First, assure 0 <= padLen <= 64
mov rdi, rdx
xor rsi, rsi
mov rdx, 64
call clamp
mov r14, rax
; Store padding character
mov rdi, ' '
mov rdx, '0'
test r15, r15
cmovnz rdi, rdx
mov r11, rdi
; Prepare for conversion
mov rdi, r13
test r13, r13
jns .continuePrepare
not rdi
.continuePrepare:
mov rax, r12
xor r9, r9
lea r10, [rel cnvtBuffRev]
mov rdx, 'a'-10
test rbx, rbx
cmovz r8, rdx
jz .convert
mov r8, 'A'-10
.convert:
xor rdx, rdx
div rdi
cmp dl, 9
jle .noLetter
add dl, r8b
jmp .write
.noLetter:
add dl, '0'
.write:
mov byte [r10], dl
inc r10
inc r9
test rax, rax
jnz .convert
; Insert prefix if needed
test r13, r13 ;prefix needed? (MSB not set)
jns .checkPadding
test r15, r15 ;prefix now? (padding with spaces; r15 is zero)
jnz .checkPadding
insert_prefix
.checkPadding:
; Substract length of converted number from padLen
sub r14, r9
cmp r14, 0
jle .checkPrefix
mov rcx, r14
test r13, r13
jns .addPadding
test r15, r15
jz .addPadding
sub rcx, 2
cmp rcx, 2
jl .checkPrefix
.addPadding:
mov byte [r10], r11b
inc r10
inc r9
loop .addPadding
; Insert prefix if needed
.checkPrefix:
test r13, r13 ;prefix needed? (MSB not set)
jns .makeString
test r15, r15 ;prefix now? (padding with zeroes; r15 is not zero)
jz .makeString
insert_prefix
.makeString:
dec r10 ;don't point to past last character
lea rdi, [rel cnvtBuff]
mov rcx, r9
xor rax, rax
.makeStringLoop:
mov al, byte [r10]
mov byte [rdi], al
inc rdi
dec r10
loop .makeStringLoop
mov byte [rdi], EOS
pop r15
pop r14
pop r13
pop r12
pop rbx
lea rax, [rel cnvtBuff]
ret