From 40e4272cddaede082ef73469fa53623d65314490 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Tue, 8 Sep 2026 17:00:26 +0200 Subject: [PATCH 1/5] Int to string conversion Initially only binary output --- src/errno.asm | 5 ++- src/int_to_string.asm | 66 ++++++++++++++++++++++++++++++++++++ src/stdlib.asm | 2 ++ src/string.asm | 33 ++++++++++++++++++ tests/int_to_string_test.asm | 49 ++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/int_to_string.asm create mode 100644 src/string.asm create mode 100644 tests/int_to_string_test.asm 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..3b30768 --- /dev/null +++ b/src/int_to_string.asm @@ -0,0 +1,66 @@ +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 +mov flags, errno.INTTOSTR_BAD_BASE +jmp r13 + +; Convert integer to binary string +; Arguments: +; r1 - Integer +; r2 - Start of buffer (assumed 'big enough') +; r3 - Buffer length (ignored) +; Result: +; none +; Clobbers: +; flags +; r4 - counter +; r5 - result 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, 0 ; 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, 0 ; 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 + +badbuffer: +mov flags, errno.INTTOSTR_BAD_BUFFER +jmp r13 + +done: +store_8 [r2], zr +jmp r13 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..2da2da9 --- /dev/null +++ b/tests/int_to_string_test.asm @@ -0,0 +1,49 @@ +; 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: + +; 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 +end_of_tests: +scratch: U1024 0 + -- 2.54.0 From 431e00ca7efb5031cb59739c74e462da800b5688 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Thu, 10 Sep 2026 15:33:46 +0200 Subject: [PATCH 2/5] Add octal support --- src/int_to_string.asm | 64 +++++++++++++++++++++++++++++++----- tests/int_to_string_test.asm | 5 ++- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/int_to_string.asm b/src/int_to_string.asm index 3b30768..b67f271 100644 --- a/src/int_to_string.asm +++ b/src/int_to_string.asm @@ -13,20 +13,33 @@ include errno pub auto: cmp r4, 2 je bin +cmp r4, 8 +je oct 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 (assumed 'big enough') -; r3 - Buffer length (ignored) +; r2 - Start of buffer +; r3 - Buffer length ; Result: ; none ; Clobbers: ; flags ; r4 - counter -; r5 - result character +; r5 - character pub bin: mov r4, 31 cmp r2, 2 @@ -57,10 +70,43 @@ and r5, r5, 1 sub r4, r4, 1 jmp bin_print -badbuffer: -mov flags, errno.INTTOSTR_BAD_BUFFER -jmp r13 +; 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 -done: -store_8 [r2], zr -jmp r13 +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, 0 ; 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, 0 ; 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 diff --git a/tests/int_to_string_test.asm b/tests/int_to_string_test.asm index 2da2da9..51a1424 100644 --- a/tests/int_to_string_test.asm +++ b/tests/int_to_string_test.asm @@ -34,6 +34,7 @@ jl next_test ; Done halt + include ../src/stdlib @0x20000 @@ -43,7 +44,9 @@ tests: @0x20000 U32 0b0 U8 2 "0\0" @0x20040 U32 0xFFFFFFFF U8 2 "11111111111111111111111111111111\0" @0x20080 U32 0b101010 U8 2 "101010\0" -@0x200c0 +@0x200c0 U32 0o640 U8 8 "640\0" +@0x20100 U32 0xFFFFFFFF U8 8 "37777777777\0" +@0x20140 end_of_tests: scratch: U1024 0 -- 2.54.0 From ae5f90456c1e1e0c745c2ffa5ab8add1f0c28d43 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Thu, 10 Sep 2026 21:03:45 +0200 Subject: [PATCH 3/5] Add hex --- src/int_to_string.asm | 46 ++++++++++++++++++++++++++++++++++++ tests/int_to_string_test.asm | 12 +++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/int_to_string.asm b/src/int_to_string.asm index b67f271..ceb166f 100644 --- a/src/int_to_string.asm +++ b/src/int_to_string.asm @@ -15,6 +15,8 @@ cmp r4, 2 je bin cmp r4, 8 je oct +cmp r4, 16 +je hex mov flags, errno.INTTOSTR_BAD_BASE jmp r13 @@ -110,3 +112,47 @@ 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, 0 ; 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, 0 ; 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 diff --git a/tests/int_to_string_test.asm b/tests/int_to_string_test.asm index 51a1424..e9b76cd 100644 --- a/tests/int_to_string_test.asm +++ b/tests/int_to_string_test.asm @@ -15,6 +15,7 @@ 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 @@ -44,9 +45,14 @@ tests: @0x20000 U32 0b0 U8 2 "0\0" @0x20040 U32 0xFFFFFFFF U8 2 "11111111111111111111111111111111\0" @0x20080 U32 0b101010 U8 2 "101010\0" -@0x200c0 U32 0o640 U8 8 "640\0" -@0x20100 U32 0xFFFFFFFF U8 8 "37777777777\0" -@0x20140 +@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 end_of_tests: scratch: U1024 0 -- 2.54.0 From 016c6f7008b59f3d35b45f5b8f263ae3cd721265 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Tue, 15 Sep 2026 21:11:14 +0200 Subject: [PATCH 4/5] Add decimal for small (up to 8 digit) numbers --- src/int_to_string.asm | 93 +++++++++++++++++++++++++++++++++--- tests/int_to_string_test.asm | 5 +- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/int_to_string.asm b/src/int_to_string.asm index ceb166f..567c609 100644 --- a/src/int_to_string.asm +++ b/src/int_to_string.asm @@ -17,6 +17,8 @@ cmp r4, 8 je oct cmp r4, 16 je hex +cmp r4, 10 +jmp dec mov flags, errno.INTTOSTR_BAD_BASE jmp r13 @@ -52,7 +54,7 @@ lsr r5, r1, r4 ; Select bit and advance counter and r5, r5, 1 sub r4, r4, 1 -cmp r4, 0 ; Start printing if last bit ... +cmp r4, zr ; Start printing if last bit ... jl bin_print cmp r5, zr ; .. or if nonzero je bin_find_one @@ -65,7 +67,7 @@ bin_print: add r5, r5, 0x30 ; '0' ; Add digit to buffer store_8 [r2], r5 add r2, r2, 1 -cmp r4, 0 ; Done if last bit +cmp r4, zr ; Done if last bit jl done lsr r5, r1, r4 ; Select bit and advance counter and r5, r5, 1 @@ -94,7 +96,7 @@ and r5, r5, 7 sub r4, r4, 3 sub r6, r6, 1 -cmp r4, 0 ; Start printing if last bit ... +cmp r4, zr ; Start printing if last bit ... jl oct_print cmp r5, zr ; .. or if nonzero je oct_find_one @@ -106,7 +108,7 @@ oct_print: add r5, r5, 0x30 ; '0' ; Add digit to buffer store_8 [r2], r5 add r2, r2, 1 -cmp r4, 0 ; Done if last bit +cmp r4, zr ; Done if last bit jl done lsr r5, r1, r4 ; Select bit and advance counter and r5, r5, 7 @@ -132,7 +134,7 @@ lsr r5, r1, r4 ; Select bit and advance counter and r5, r5, 0xF sub r4, r4, 4 -cmp r4, 0 ; Start printing if last bit ... +cmp r4, zr ; Start printing if last bit ... jl hex_print cmp r5, zr ; .. or if nonzero je hex_find_one @@ -150,9 +152,88 @@ add r5, r5, 0x07 ; 'A' - '0' - 10 hex_no_letter: store_8 [r2], r5 add r2, r2, 1 -cmp r4, 0 ; Done if last bit +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 +jl 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/tests/int_to_string_test.asm b/tests/int_to_string_test.asm index e9b76cd..ecac19b 100644 --- a/tests/int_to_string_test.asm +++ b/tests/int_to_string_test.asm @@ -52,7 +52,10 @@ tests: @0x201c0 U32 0x32fF6 U8 16 "32FF6\0" @0x20200 U32 0xdeadbeef U8 16 "DEADBEEF\0" @0x20240 U32 0xFFFFFFFF U8 16 "FFFFFFFF\0" -@0x20280 +@0x20280 U32 0 U8 10 "0\0" +@0x202c0 U32 42069 U8 10 "42069\0" +@0x20300 U32 12345678 U8 10 "12345678\0" +@0x20340 end_of_tests: scratch: U1024 0 -- 2.54.0 From 836acb48af4adfb1df309ebfef43349cc2385caa Mon Sep 17 00:00:00 2001 From: PleegWat Date: Tue, 15 Sep 2026 21:23:25 +0200 Subject: [PATCH 5/5] Add test and fix for edgecase --- src/int_to_string.asm | 4 ++-- tests/int_to_string_test.asm | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/int_to_string.asm b/src/int_to_string.asm index 567c609..db5b197 100644 --- a/src/int_to_string.asm +++ b/src/int_to_string.asm @@ -184,7 +184,7 @@ jl dec_small ; Utilizes double dabble ; r4, r5 are loop counters ; -; for( r4 = 24 ; r4 >= 0 ; r4-- ) +; for( r4 = 24 ; r4 > 0 ; r4-- ) ; for( r5 = r4 ; r5 <= 28 ; r5+= 4 ) ; if( ((r1 >> r5) & 0xF) >= 5 ) ; r1 += 3 << r5 @@ -203,7 +203,7 @@ add r1, r1, flags dec_small_dabble_noinc: add r5, r5, 4 cmp r5, 28 -jl dec_small_dabble_inner +jle dec_small_dabble_inner sub r4, r4, 1 cmp r4, zr jg dec_small_dabble_outer diff --git a/tests/int_to_string_test.asm b/tests/int_to_string_test.asm index ecac19b..786a6f3 100644 --- a/tests/int_to_string_test.asm +++ b/tests/int_to_string_test.asm @@ -55,7 +55,8 @@ tests: @0x20280 U32 0 U8 10 "0\0" @0x202c0 U32 42069 U8 10 "42069\0" @0x20300 U32 12345678 U8 10 "12345678\0" -@0x20340 +@0x20340 U32 99999999 U8 10 "99999999\0" +@0x20380 end_of_tests: scratch: U1024 0 -- 2.54.0