diff --git a/src/errno.asm b/src/errno.asm index 5d06c89..287553d 100755 --- a/src/errno.asm +++ b/src/errno.asm @@ -21,7 +21,10 @@ pub const SCREEN_INVALID_WIDTH = 0x0003 pub const SCREEN_FB_TOO_SMALL = 0x0005 pub const SCREEN_OUTSIDE_FB = 0x0007 +pub const INTTOSTR_BAD_BASE = 0x0101 +pub const INTTOSTR_BAD_BUFFER = 0x0103 + pub const MAGIC_BAD = 0x8001 ; Extended error codes, these would require loading a 32 bit value. -pub const EXT_OK = 0x00000000 \ No newline at end of file +pub const EXT_OK = 0x00000000 diff --git a/src/int_to_string.asm b/src/int_to_string.asm new file mode 100644 index 0000000..db5b197 --- /dev/null +++ b/src/int_to_string.asm @@ -0,0 +1,239 @@ +include errno + +; Convert integer to string +; Arguments: +; r1 - Integer +; r2 - Start of buffer +; r3 - Buffer length +; r4 - Base (2, 8, 10, or 16) +; Result: +; none +; Clobbers: +; Based on specialization +pub auto: +cmp r4, 2 +je bin +cmp r4, 8 +je oct +cmp r4, 16 +je hex +cmp r4, 10 +jmp dec +mov flags, errno.INTTOSTR_BAD_BASE +jmp r13 + +; The output buffer is too small for this integer +badbuffer: +mov flags, errno.INTTOSTR_BAD_BUFFER +jmp r13 + +; All digits have been output. Null terminate and exit. +done: +store_8 [r2], zr +mov flags, errno.OK +jmp r13 + +; Convert integer to binary string +; Arguments: +; r1 - Integer +; r2 - Start of buffer +; r3 - Buffer length +; Result: +; none +; Clobbers: +; flags +; r4 - counter +; r5 - character +pub bin: +mov r4, 31 +cmp r2, 2 +jl badbuffer + +bin_find_one: +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 1 +sub r4, r4, 1 + +cmp r4, zr ; Start printing if last bit ... +jl bin_print +cmp r5, zr ; .. or if nonzero +je bin_find_one + +add flags, r4, 3 ; Check buffer size +cmp flags, r3 +ja badbuffer + +bin_print: +add r5, r5, 0x30 ; '0' ; Add digit to buffer +store_8 [r2], r5 +add r2, r2, 1 +cmp r4, zr ; Done if last bit +jl done +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 1 +sub r4, r4, 1 +jmp bin_print + +; Convert integer to octal string +; Arguments: +; r1 - Integer +; r2 - Start of buffer +; r3 - Buffer length +; Result: +; none +; Clobbers: +; flags +; r4 - input counter +; r5 - character +; r6 - output size counter +pub oct: +mov r4, 30 +mov r6, 12 + +oct_find_one: +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 7 +sub r4, r4, 3 +sub r6, r6, 1 + +cmp r4, zr ; Start printing if last bit ... +jl oct_print +cmp r5, zr ; .. or if nonzero +je oct_find_one + +cmp r6, r3 ; Check buffer size +ja badbuffer + +oct_print: +add r5, r5, 0x30 ; '0' ; Add digit to buffer +store_8 [r2], r5 +add r2, r2, 1 +cmp r4, zr ; Done if last bit +jl done +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 7 +sub r4, r4, 3 +jmp oct_print + +; Convert integer to hexadecimal string +; Arguments: +; r1 - Integer +; r2 - Start of buffer +; r3 - Buffer length +; Result: +; none +; Clobbers: +; flags +; r4 - input counter +; r5 - character +pub hex: +mov r4, 28 + +hex_find_one: +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 0xF +sub r4, r4, 4 + +cmp r4, zr ; Start printing if last bit ... +jl hex_print +cmp r5, zr ; .. or if nonzero +je hex_find_one + +lsr flags, r5, 2 ; Check buffer size +add flags, flags, 3 +cmp flags, r3 +ja badbuffer + +hex_print: +add r5, r5, 0x30 ; '0' ; Add digit to buffer +cmp r5, 0x39 ; '9' +jle hex_no_letter +add r5, r5, 0x07 ; 'A' - '0' - 10 +hex_no_letter: +store_8 [r2], r5 +add r2, r2, 1 +cmp r4, zr ; Done if last bit +jl done +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 0xF +sub r4, r4, 4 +jmp hex_print + +; Convert integer to decimal string +; Arguments: +; r1 - Integer +; r2 - Start of buffer +; r3 - Buffer length +; Result: +; none +; Clobbers: +; flags +; r4, r5 +pub dec: +mov r4, 0x5F5 +lsl r4, r4, 16 +add r4, r4, 0xE100 ; 100 million; 8 digits +cmp r1, r4 +jl dec_small +; TODO large + + +; Small numbers have at least most 8 digits, thus their BCD form fits in +; one register. Bit 26 is the highest which can be set. +; +; Utilizes double dabble +; r4, r5 are loop counters +; +; for( r4 = 24 ; r4 > 0 ; r4-- ) +; for( r5 = r4 ; r5 <= 28 ; r5+= 4 ) +; if( ((r1 >> r5) & 0xF) >= 5 ) +; r1 += 3 << r5 +dec_small: +mov r4, 24 +dec_small_dabble_outer: +mov r5, r4 +dec_small_dabble_inner: +lsr flags, r1, r5 +and flags, flags, 0xF +cmp flags, 5 +jl dec_small_dabble_noinc +mov flags, 3 +lsl flags, flags, r5 +add r1, r1, flags +dec_small_dabble_noinc: +add r5, r5, 4 +cmp r5, 28 +jle dec_small_dabble_inner +sub r4, r4, 1 +cmp r4, zr +jg dec_small_dabble_outer + +; Print the BCD in r1 +; r4 is a counter +; r5 is the output character +mov r4, 28 +dec_find_one: +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 0xF +sub r4, r4, 4 + +cmp r4, zr ; Start printing if last bit ... +jl dec_print +cmp r5, zr ; .. or if nonzero +je dec_find_one + +lsr flags, r5, 2 ; Check buffer size +add flags, flags, 3 +cmp flags, r3 +ja badbuffer + +dec_print: +add r5, r5, 0x30 ; '0' ; Add digit to buffer +store_8 [r2], r5 +add r2, r2, 1 +cmp r4, zr ; Done if last bit +jl done +lsr r5, r1, r4 ; Select bit and advance counter +and r5, r5, 0xF +sub r4, r4, 4 +jmp dec_print diff --git a/src/stdlib.asm b/src/stdlib.asm index ee6477d..ad85881 100644 --- a/src/stdlib.asm +++ b/src/stdlib.asm @@ -3,7 +3,9 @@ pub include imath pub include array pub include console pub include mem +pub include string pub include string_to_int +pub include int_to_string ; Needs to be last! pub include LUTs diff --git a/src/string.asm b/src/string.asm new file mode 100644 index 0000000..26c99ec --- /dev/null +++ b/src/string.asm @@ -0,0 +1,33 @@ +; Compare two strings +; Arguments: +; r1 - Pointer to string 1 +; r2 - Pointer to string 2 +; Result: +; r1: +; - `0` if both segments are equal. +; - `<0` if the first segment is less than the second segment. +; - `>0` if the first segment is greater than the second segment. +; flags - Compare result +; Clobbers: +; r3 - last byte of r1 tested +; r4 - last byte of r2 tested +pub compare: +load_8 r3, [r1] +add r1, r1, 1 +load_8 r4, [r2] +add r2, r2, 1 +cmp zr, r3 ; End of string 1 +je done +cmp zr, r4 ; End of string 2 +je done +cmp r3, r4 +je compare ; Loop, tail recursion, what's the difference +done: +cmp r3, r4 +; `flags` is the comparison result in the format of `cmp`. Convert it to the desired format. +; 00 => 0x40000000 > 0 +; 01 => 0x00000000 = 0 +; 10 => 0xC0000000 < 0 +xor r1, flags, 1 +lsl r1, r1, 30 +jmp r13 diff --git a/tests/int_to_string_test.asm b/tests/int_to_string_test.asm new file mode 100644 index 0000000..786a6f3 --- /dev/null +++ b/tests/int_to_string_test.asm @@ -0,0 +1,62 @@ +; test harness for int_to_string +; Registers: +; r8 - test address +; r9 - test method +; r12 - end of tests address +li r8, tests +li r12, end_of_tests +next_test: +; Set up arguments and run test +load_32 r1, [r8] ; Input value +li r2, scratch ; Output buffer +mov r3, 64 ; Output buffer size +add r4, r8, 4 ; Base +load_8 r4, [r4] +mov r13, itoa_test_ret ; return address +jmp stdlib.int_to_string.auto +itoa_test_ret: +nop ; For breakpoint after itoa but before r1 clobber + +; Strcmp to check results +add r1, r8, 5 ; expected output +li r2, scratch ; actual output +mov r13, itoa_strcmp_ret +jmp stdlib.string.compare +itoa_strcmp_ret: + +; Verify results +halt.ne + +; Next test +add r8, r8, 0x40 +cmp r8, r12 +jl next_test + +; Done +halt + + +include ../src/stdlib + +@0x20000 +tests: +; inlen is the number of input bytes the function is expected to consume +; addr int base result +@0x20000 U32 0b0 U8 2 "0\0" +@0x20040 U32 0xFFFFFFFF U8 2 "11111111111111111111111111111111\0" +@0x20080 U32 0b101010 U8 2 "101010\0" +@0x200c0 U32 0o0 U8 8 "0\0" +@0x20100 U32 0o640 U8 8 "640\0" +@0x20140 U32 0xFFFFFFFF U8 8 "37777777777\0" +@0x20180 U32 0x0 U8 16 "0\0" +@0x201c0 U32 0x32fF6 U8 16 "32FF6\0" +@0x20200 U32 0xdeadbeef U8 16 "DEADBEEF\0" +@0x20240 U32 0xFFFFFFFF U8 16 "FFFFFFFF\0" +@0x20280 U32 0 U8 10 "0\0" +@0x202c0 U32 42069 U8 10 "42069\0" +@0x20300 U32 12345678 U8 10 "12345678\0" +@0x20340 U32 99999999 U8 10 "99999999\0" +@0x20380 +end_of_tests: +scratch: U1024 0 +