From 6f7ad3aa4bd9bc4d69d05162ab823ddc24ae3178 Mon Sep 17 00:00:00 2001 From: PleegWat Date: Wed, 2 Sep 2026 21:11:45 +0200 Subject: [PATCH] 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: +