From 6f7ad3aa4bd9bc4d69d05162ab823ddc24ae3178 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Wed, 2 Sep 2026 21:11:45 +0200 Subject: [PATCH 1/2] Implement atoi Parse strings to integer (binary, octal, decimal, or hexadecimal) --- src/stdlib.asm | 1 + src/strint.asm | 188 ++++++++++++++++++++++++++++++++++++++++++ tests/strint_test.asm | 57 +++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 src/strint.asm create mode 100644 tests/strint_test.asm diff --git a/src/stdlib.asm b/src/stdlib.asm index 512c6c3..31fb521 100644 --- a/src/stdlib.asm +++ b/src/stdlib.asm @@ -3,6 +3,7 @@ pub include imath pub include array pub include console pub include mem +pub include strint ; Needs to be last! pub include LUTs diff --git a/src/strint.asm b/src/strint.asm new file mode 100644 index 0000000..752b74f --- /dev/null +++ b/src/strint.asm @@ -0,0 +1,188 @@ +; Internal register assignments: +; r1 - Partially parsed output integer +; r2 - Parsing position +; r3 - Character being parsed +; r4 - Set to -1 if the input is negative + +; Convert string to integer +; Arguments: +; r1 - Pointer to string +; Result: +; r1 - Parsed integer +; r2 - Pointer to first rejected input byte +; Clobbers: +; flags +; r3 - last character read +; r4 - Negative marker +; Note: Unless the input is "0", tail-calls into a base-specific specialization. +pub atoi: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne atoi_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +atoi_positive: +load_16 r3, [r2] ; 2-byte prefix "0b", "0o", "0x", etc. +or r3, r3, 0x20 ; 2nd char to lower case +add r2, r2, 2 +cmp r3, 0x3062 ; "0b" +je atoi_bin_loop +cmp r3, 0x306F ; "0o" +je atoi_oct_loop +cmp r3, 0x3078 ; "0x" +je atoi_hex_loop +sub r2, r2, 2 ; no matching prefix, move pointer back +jmp atoi_dec_loop + +; Epilogue +atoi_done: +add r1, r1, r4 ; If r4 is -1, negate r1. Else it's 0 and no effect. +xor r1, r1, r4 +mov flags, 0 ; No error +jmp r13 + +; Convert decimal string to integer +; Arguments: +; r1 - Pointer to string +; Result: +; r1 - Parsed integer +; r2 - Pointer to first rejected input byte +; Clobbers: +; flags +; r3 - last character read +; r4 - Negative marker +pub atoi_dec: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne atoi_dec_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +atoi_dec_loop: +load_8 r3, [r2] +atoi_dec_positive: +sub r3, r3, 0x30 ; '0' +cmp r3, 9 +ja atoi_done +add r2, r2, 1 +lsl flags, r1, 2 ; Use flags to help multiply by 10 +add r1, r1, flags +lsl r1, r1, 1 +add r1, r1, r3 +jmp atoi_dec_loop + +; Convert binary string to integer +; Arguments: +; r1 - Pointer to string +; Result: +; r1 - Parsed integer +; r2 - Pointer to first rejected input byte +; Clobbers: +; flags +; r3 - last character read +; r4 - Negative marker +pub atoi_bin: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne atoi_bin_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +atoi_bin_positive: +load_16 r3, [r2] ; check for prefix +or r3, r3, 0x20 ; 2nd char to lower case +cmp r3, 0x3062 ; "0b" +jne atoi_bin_loop +add r2, r2, 2 +atoi_bin_loop: +load_8 r3, [r2] +sub r3, r3, 0x30 ; '0' +cmp r3, 1 +ja atoi_done +add r2, r2, 1 +lsl r1, r1, 1 +add r1, r1, r3 +jmp atoi_bin_loop + +; Convert octal string to integer +; Arguments: +; r1 - Pointer to string +; Result: +; r1 - Parsed integer +; r2 - Pointer to first rejected input byte +; Clobbers: +; flags +; r3 - last character read +; r4 - Negative marker +pub atoi_oct: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne atoi_oct_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +atoi_oct_positive: +load_16 r3, [r2] ; check for prefix +or r3, r3, 0x20 ; 2nd char to lower case +cmp r3, 0x306F ; "0o" +jne atoi_oct_loop +add r2, r2, 2 +atoi_oct_loop: +load_8 r3, [r2] +sub r3, r3, 0x30 ; '0' +cmp r3, 7 +ja atoi_done +add r2, r2, 1 +lsl r1, r1, 3 +add r1, r1, r3 +jmp atoi_oct_loop + +; Convert hexadecimal string to integer +; Arguments: +; r1 - Pointer to string +; Result: +; r1 - Parsed integer +; r2 - Pointer to first rejected input byte +; Clobbers: +; flags +; r3 - last character read +; r4 - Negative marker +pub atoi_hex: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne atoi_hex_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +atoi_hex_positive: +load_16 r3, [r2] ; check for prefix +or r3, r3, 0x20 ; 2nd char to lower case +cmp r3, 0x3078 ; "0x" +jne atoi_hex_loop +add r2, r2, 2 +atoi_hex_loop: +load_8 r3, [r2] +sub r3, r3, 0x30 ; '0' +cmp r3, 10 +jb atoi_hex_add +sub r3, r3, 0x11 ; 'A' - '0' +and r3, r3, 0xdf ; to lower case +cmp r3, 5 ; 0-5: 6 letters +ja atoi_done +add r3, r3, 10 ; Adjust for digits below +atoi_hex_add: +add r2, r2, 1 +lsl r1, r1, 4 +add r1, r1, r3 +jmp atoi_hex_loop diff --git a/tests/strint_test.asm b/tests/strint_test.asm new file mode 100644 index 0000000..3e7ccb2 --- /dev/null +++ b/tests/strint_test.asm @@ -0,0 +1,57 @@ +; test harness for strint.atoi +; Registers: +; r8 - test address +; r9 - expected result +; r10 - actual length +; r11 - expected length +li r8, tests +li r12, end_of_tests +next_test: +; Set up arguments and run test +add r1, r8, 5 ; Start of test string +mov r13, atoi_test_ret +jmp stdlib.strint.atoi +atoi_test_ret: + +; Load reference data +load_32 r9, [r8] ; Expected result +sub r10, r2, r8 +sub r10, r10, 5 ; Actual string length +add r11, r8, 4 ; Address of expected length +load_8 r11, [r11] ; Expected length + +; Verify results +cmp r1, r9 +incorrect_result: jne incorrect_result +cmp r10, r11 +incorrect_length: jne incorrect_length + +; Next test +add r8, r8, 0x10 +cmp r8, r12 +jl next_test + +; Done +success: jmp success + +include ../src/stdlib + +@0x20000 +tests: +; inlen is the number of input bytes the function is expected to consume +; addr result inlen instr +@0x20000 U32 0 U8 0 "\0" +@0x20010 U32 0 U8 1 "0\0" +@0x20020 U32 1 U8 1 "1\0" +@0x20030 U32 2 U8 1 "2:\0" +@0x20040 U32 42 U8 2 "42\0" +@0x20050 U32 67 U8 2 "67lol\0" +@0x20060 U32 0x69a U8 5 "0x69a@\0" +@0x20070 U32 0x4B4 U8 5 "0x4B4g\0" +@0x20080 U32 0o23 U8 4 "0o239\0" +@0x20090 U32 0b1011 U8 6 "0b1011\0" +@0x200a0 U32 0b10001 U8 7 "0b100012\0" +@0x200b0 U32 0xFFFFFFEB U8 3 "-21\0" ; Yuk +@0x200c0 +end_of_tests: + From 304fe5483390b861fcd860598722fc0e2a249cf6 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Fri, 4 Sep 2026 23:53:46 +0200 Subject: [PATCH 2/2] Apply agreed naming conventions --- src/stdlib.asm | 2 +- src/{strint.asm => string_to_int.asm} | 76 +++++++++---------- ...strint_test.asm => string_to_int_test.asm} | 4 +- 3 files changed, 41 insertions(+), 41 deletions(-) rename src/{strint.asm => string_to_int.asm} (83%) rename tests/{strint_test.asm => string_to_int_test.asm} (95%) diff --git a/src/stdlib.asm b/src/stdlib.asm index 31fb521..ee6477d 100644 --- a/src/stdlib.asm +++ b/src/stdlib.asm @@ -3,7 +3,7 @@ pub include imath pub include array pub include console pub include mem -pub include strint +pub include string_to_int ; Needs to be last! pub include LUTs diff --git a/src/strint.asm b/src/string_to_int.asm similarity index 83% rename from src/strint.asm rename to src/string_to_int.asm index 752b74f..cd961c2 100644 --- a/src/strint.asm +++ b/src/string_to_int.asm @@ -4,7 +4,7 @@ ; r3 - Character being parsed ; r4 - Set to -1 if the input is negative -; Convert string to integer +; Convert string to integer (akin to libc atoi(), strotoi()) ; Arguments: ; r1 - Pointer to string ; Result: @@ -15,30 +15,30 @@ ; r3 - last character read ; r4 - Negative marker ; Note: Unless the input is "0", tail-calls into a base-specific specialization. -pub atoi: +pub auto: mov r2, r1 mov r1, 0 mov r4, 0 load_8 r3, [r2] cmp r3, 0x2D ; '-' -jne atoi_positive +jne auto_positive sub r4, zr, 1 ; Set r4 to -1 add r2, r2, 1 -atoi_positive: +auto_positive: load_16 r3, [r2] ; 2-byte prefix "0b", "0o", "0x", etc. or r3, r3, 0x20 ; 2nd char to lower case add r2, r2, 2 cmp r3, 0x3062 ; "0b" -je atoi_bin_loop +je bin_loop cmp r3, 0x306F ; "0o" -je atoi_oct_loop +je oct_loop cmp r3, 0x3078 ; "0x" -je atoi_hex_loop +je hex_loop sub r2, r2, 2 ; no matching prefix, move pointer back -jmp atoi_dec_loop +jmp dec_loop ; Epilogue -atoi_done: +done: add r1, r1, r4 ; If r4 is -1, negate r1. Else it's 0 and no effect. xor r1, r1, r4 mov flags, 0 ; No error @@ -54,27 +54,27 @@ jmp r13 ; flags ; r3 - last character read ; r4 - Negative marker -pub atoi_dec: +pub dec: mov r2, r1 mov r1, 0 mov r4, 0 load_8 r3, [r2] cmp r3, 0x2D ; '-' -jne atoi_dec_positive +jne dec_positive sub r4, zr, 1 ; Set r4 to -1 add r2, r2, 1 -atoi_dec_loop: +dec_loop: load_8 r3, [r2] -atoi_dec_positive: +dec_positive: sub r3, r3, 0x30 ; '0' cmp r3, 9 -ja atoi_done +ja done add r2, r2, 1 lsl flags, r1, 2 ; Use flags to help multiply by 10 add r1, r1, flags lsl r1, r1, 1 add r1, r1, r3 -jmp atoi_dec_loop +jmp dec_loop ; Convert binary string to integer ; Arguments: @@ -86,30 +86,30 @@ jmp atoi_dec_loop ; flags ; r3 - last character read ; r4 - Negative marker -pub atoi_bin: +pub bin: mov r2, r1 mov r1, 0 mov r4, 0 load_8 r3, [r2] cmp r3, 0x2D ; '-' -jne atoi_bin_positive +jne bin_positive sub r4, zr, 1 ; Set r4 to -1 add r2, r2, 1 -atoi_bin_positive: +bin_positive: load_16 r3, [r2] ; check for prefix or r3, r3, 0x20 ; 2nd char to lower case cmp r3, 0x3062 ; "0b" -jne atoi_bin_loop +jne bin_loop add r2, r2, 2 -atoi_bin_loop: +bin_loop: load_8 r3, [r2] sub r3, r3, 0x30 ; '0' cmp r3, 1 -ja atoi_done +ja done add r2, r2, 1 lsl r1, r1, 1 add r1, r1, r3 -jmp atoi_bin_loop +jmp bin_loop ; Convert octal string to integer ; Arguments: @@ -121,30 +121,30 @@ jmp atoi_bin_loop ; flags ; r3 - last character read ; r4 - Negative marker -pub atoi_oct: +pub oct: mov r2, r1 mov r1, 0 mov r4, 0 load_8 r3, [r2] cmp r3, 0x2D ; '-' -jne atoi_oct_positive +jne oct_positive sub r4, zr, 1 ; Set r4 to -1 add r2, r2, 1 -atoi_oct_positive: +oct_positive: load_16 r3, [r2] ; check for prefix or r3, r3, 0x20 ; 2nd char to lower case cmp r3, 0x306F ; "0o" -jne atoi_oct_loop +jne oct_loop add r2, r2, 2 -atoi_oct_loop: +oct_loop: load_8 r3, [r2] sub r3, r3, 0x30 ; '0' cmp r3, 7 -ja atoi_done +ja done add r2, r2, 1 lsl r1, r1, 3 add r1, r1, r3 -jmp atoi_oct_loop +jmp oct_loop ; Convert hexadecimal string to integer ; Arguments: @@ -156,33 +156,33 @@ jmp atoi_oct_loop ; flags ; r3 - last character read ; r4 - Negative marker -pub atoi_hex: +pub hex: mov r2, r1 mov r1, 0 mov r4, 0 load_8 r3, [r2] cmp r3, 0x2D ; '-' -jne atoi_hex_positive +jne hex_positive sub r4, zr, 1 ; Set r4 to -1 add r2, r2, 1 -atoi_hex_positive: +hex_positive: load_16 r3, [r2] ; check for prefix or r3, r3, 0x20 ; 2nd char to lower case cmp r3, 0x3078 ; "0x" -jne atoi_hex_loop +jne hex_loop add r2, r2, 2 -atoi_hex_loop: +hex_loop: load_8 r3, [r2] sub r3, r3, 0x30 ; '0' cmp r3, 10 -jb atoi_hex_add +jb hex_add sub r3, r3, 0x11 ; 'A' - '0' and r3, r3, 0xdf ; to lower case cmp r3, 5 ; 0-5: 6 letters -ja atoi_done +ja done add r3, r3, 10 ; Adjust for digits below -atoi_hex_add: +hex_add: add r2, r2, 1 lsl r1, r1, 4 add r1, r1, r3 -jmp atoi_hex_loop +jmp hex_loop diff --git a/tests/strint_test.asm b/tests/string_to_int_test.asm similarity index 95% rename from tests/strint_test.asm rename to tests/string_to_int_test.asm index 3e7ccb2..c60494b 100644 --- a/tests/strint_test.asm +++ b/tests/string_to_int_test.asm @@ -1,4 +1,4 @@ -; test harness for strint.atoi +; test harness for string_to_int ; Registers: ; r8 - test address ; r9 - expected result @@ -10,7 +10,7 @@ next_test: ; Set up arguments and run test add r1, r8, 5 ; Start of test string mov r13, atoi_test_ret -jmp stdlib.strint.atoi +jmp stdlib.string_to_int.auto atoi_test_ret: ; Load reference data