diff --git a/src/stdlib.asm b/src/stdlib.asm index 512c6c3..ee6477d 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 string_to_int ; Needs to be last! pub include LUTs diff --git a/src/string_to_int.asm b/src/string_to_int.asm new file mode 100644 index 0000000..cd961c2 --- /dev/null +++ b/src/string_to_int.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 (akin to libc atoi(), strotoi()) +; 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 auto: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne auto_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +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 bin_loop +cmp r3, 0x306F ; "0o" +je oct_loop +cmp r3, 0x3078 ; "0x" +je hex_loop +sub r2, r2, 2 ; no matching prefix, move pointer back +jmp dec_loop + +; Epilogue +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 dec: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne dec_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +dec_loop: +load_8 r3, [r2] +dec_positive: +sub r3, r3, 0x30 ; '0' +cmp r3, 9 +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 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 bin: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne bin_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +bin_positive: +load_16 r3, [r2] ; check for prefix +or r3, r3, 0x20 ; 2nd char to lower case +cmp r3, 0x3062 ; "0b" +jne bin_loop +add r2, r2, 2 +bin_loop: +load_8 r3, [r2] +sub r3, r3, 0x30 ; '0' +cmp r3, 1 +ja done +add r2, r2, 1 +lsl r1, r1, 1 +add r1, r1, r3 +jmp 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 oct: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne oct_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +oct_positive: +load_16 r3, [r2] ; check for prefix +or r3, r3, 0x20 ; 2nd char to lower case +cmp r3, 0x306F ; "0o" +jne oct_loop +add r2, r2, 2 +oct_loop: +load_8 r3, [r2] +sub r3, r3, 0x30 ; '0' +cmp r3, 7 +ja done +add r2, r2, 1 +lsl r1, r1, 3 +add r1, r1, r3 +jmp 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 hex: +mov r2, r1 +mov r1, 0 +mov r4, 0 +load_8 r3, [r2] +cmp r3, 0x2D ; '-' +jne hex_positive +sub r4, zr, 1 ; Set r4 to -1 +add r2, r2, 1 +hex_positive: +load_16 r3, [r2] ; check for prefix +or r3, r3, 0x20 ; 2nd char to lower case +cmp r3, 0x3078 ; "0x" +jne hex_loop +add r2, r2, 2 +hex_loop: +load_8 r3, [r2] +sub r3, r3, 0x30 ; '0' +cmp r3, 10 +jb hex_add +sub r3, r3, 0x11 ; 'A' - '0' +and r3, r3, 0xdf ; to lower case +cmp r3, 5 ; 0-5: 6 letters +ja done +add r3, r3, 10 ; Adjust for digits below +hex_add: +add r2, r2, 1 +lsl r1, r1, 4 +add r1, r1, r3 +jmp hex_loop diff --git a/tests/string_to_int_test.asm b/tests/string_to_int_test.asm new file mode 100644 index 0000000..c60494b --- /dev/null +++ b/tests/string_to_int_test.asm @@ -0,0 +1,57 @@ +; test harness for string_to_int +; 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.string_to_int.auto +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: +