mirror of
https://github.com/vlang/v.git
synced 2025-09-07 22:30:57 -04:00
strconv: add atou, atou8/16/32/64 utility functions with tests (#23766)
This commit is contained in:
parent
cfeb1bb564
commit
8b3d02de75
@ -1,11 +1,11 @@
|
|||||||
import strconv
|
import strconv
|
||||||
|
|
||||||
struct StrInt { // Inner test struct
|
struct StrInt { // test struct
|
||||||
str_value string
|
str_value string
|
||||||
int_value int
|
int_value int
|
||||||
}
|
}
|
||||||
|
|
||||||
// test what should be catch by atoi_common_check
|
// test what should be caught by atoi_common_check
|
||||||
fn test_common_check() {
|
fn test_common_check() {
|
||||||
// Parsing of these strings should fail on all types.
|
// Parsing of these strings should fail on all types.
|
||||||
ko := [
|
ko := [
|
||||||
|
95
vlib/strconv/atou.v
Normal file
95
vlib/strconv/atou.v
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) 2019-2024 V language community. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
module strconv
|
||||||
|
|
||||||
|
// atou_common_check perform basics check on unsigned string to parse.
|
||||||
|
// Test emptiness, + sign presence, absence of minus sign, presence of digit after
|
||||||
|
// signs and no underscore as first character.
|
||||||
|
// returns s first digit index or an error.
|
||||||
|
@[direct_array_access]
|
||||||
|
fn atou_common_check(s string) !int {
|
||||||
|
if s == '' {
|
||||||
|
return error('strconv.atou: parsing "": empty string')
|
||||||
|
}
|
||||||
|
|
||||||
|
mut start_idx := 0
|
||||||
|
|
||||||
|
if s[0] == `-` {
|
||||||
|
return error('strconv.atou: parsing "{s}" : negative value')
|
||||||
|
}
|
||||||
|
|
||||||
|
if s[0] == `+` {
|
||||||
|
start_idx++
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.len - start_idx < 1 {
|
||||||
|
return error('strconv.atou: parsing "${s}": no number after sign')
|
||||||
|
}
|
||||||
|
|
||||||
|
if s[start_idx] == `_` || s[s.len - 1] == `_` {
|
||||||
|
return error('strconv.atou: parsing "${s}": values cannot start or end with underscores')
|
||||||
|
}
|
||||||
|
return start_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou_common performs computation for all u8, u16 and u32 type, excluding i64.
|
||||||
|
// Parse values, and returns consistent error message over differents types.
|
||||||
|
// s is string to parse, max is respective types max value.
|
||||||
|
@[direct_array_access]
|
||||||
|
fn atou_common(s string, type_max u64) !u64 {
|
||||||
|
mut start_idx := atou_common_check(s)!
|
||||||
|
mut x := u64(0)
|
||||||
|
mut underscored := false
|
||||||
|
for i in start_idx .. s.len {
|
||||||
|
c := s[i] - `0`
|
||||||
|
if c == 47 { // 47 = Ascii(`_`) - ascii(`0`) = 95 - 48.
|
||||||
|
if underscored == true { // Two consecutives underscore
|
||||||
|
return error('strconv.atou: parsing "${s}": consecutives underscores are not allowed')
|
||||||
|
}
|
||||||
|
underscored = true
|
||||||
|
continue // Skip underscore
|
||||||
|
} else {
|
||||||
|
if c > 9 {
|
||||||
|
return error('strconv.atou: parsing "${s}": invalid radix 10 character')
|
||||||
|
}
|
||||||
|
underscored = false
|
||||||
|
|
||||||
|
oldx := x
|
||||||
|
x = (x * 10) + u64(c)
|
||||||
|
if x > type_max || oldx > x {
|
||||||
|
return error('strconv.atou: parsing "${s}": integer overflow')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou8 is equivalent to parse_uint(s, 10, 0), converted to type u8.
|
||||||
|
// It returns u8 in range [0..255] or an Error.
|
||||||
|
pub fn atou8(s string) !u8 {
|
||||||
|
return u8(atou_common(s, max_u8)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou16 is equivalent to parse_uint(s, 10, 0), converted to type u16.
|
||||||
|
// It returns u16 in range [0..65535] or an Error.
|
||||||
|
pub fn atou16(s string) !u16 {
|
||||||
|
return u16(atou_common(s, max_u16)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou is equivalent to parse_uint(s, 10, 0), converted to type u32.
|
||||||
|
pub fn atou(s string) !u32 {
|
||||||
|
return u32(atou_common(s, max_u32)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou32 is identical to atou. Here to provide a symetrical API with atoi/atoi32
|
||||||
|
// It returns u32 in range [0..4294967295] or an Error.
|
||||||
|
pub fn atou32(s string) !u32 {
|
||||||
|
return u32(atou_common(s, max_u32)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou64 is equivalent to parse_uint(s, 10, 0), converted to type u64.
|
||||||
|
// It returns u64 in range [0..18446744073709551615] or an Error.
|
||||||
|
pub fn atou64(s string) !u64 {
|
||||||
|
return u64(atou_common(s, max_u64)!)
|
||||||
|
}
|
295
vlib/strconv/atou_test.v
Normal file
295
vlib/strconv/atou_test.v
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
// Copyright (c) 2019-2024 V language community. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
import strconv
|
||||||
|
|
||||||
|
// Perform tests against basic check done on fn test_atou_common_check()
|
||||||
|
// used from atou_common.
|
||||||
|
fn test_atou_common_check() {
|
||||||
|
// Parsing of these strings should fail on all types.
|
||||||
|
ko := [
|
||||||
|
'', // Empty string
|
||||||
|
'+', // Only sign
|
||||||
|
'-10', // - sign
|
||||||
|
'_', // Only Underscore
|
||||||
|
'_10', // Start with underscore
|
||||||
|
'+_10', // Start with underscore after sign.
|
||||||
|
'-_16', // Start with underscore after sign.
|
||||||
|
'123_', // End with underscore
|
||||||
|
'+12_3_', // Sign with trailing underscore
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou(v) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string "${v}" should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for : "${v}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs tests on possible errors from atou_common function.
|
||||||
|
// Function called from atou_common are tested above.
|
||||||
|
fn test_atou_common() {
|
||||||
|
struct StrUint {
|
||||||
|
str_value string
|
||||||
|
uint_value u64
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := [
|
||||||
|
StrUint{'0', 0},
|
||||||
|
StrUint{'+0', 0},
|
||||||
|
StrUint{'1', 1},
|
||||||
|
StrUint{'+3_14159', 314159},
|
||||||
|
StrUint{'1_00_1', 1001},
|
||||||
|
StrUint{'+1_024', 1024},
|
||||||
|
StrUint{'123_456_789', 123456789},
|
||||||
|
StrUint{'00000006', 6},
|
||||||
|
StrUint{'+0_0_0_0_0_0_0_6', 6},
|
||||||
|
StrUint{'2147483647', 2147483647},
|
||||||
|
StrUint{'+4294967295', 4294967295}, // max u32 bits
|
||||||
|
StrUint{'+18446744073709551615', 18446744073709551615}, // max u64 bits
|
||||||
|
]
|
||||||
|
|
||||||
|
// Check that extracted int value matches its string.
|
||||||
|
for v in ok {
|
||||||
|
// println('Parsing ${v.str_value} should equals ${v.int_value}')
|
||||||
|
assert strconv.atou_common(v.str_value, max_u64)! == v.uint_value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing of these values should fail !
|
||||||
|
ko := [
|
||||||
|
'+1_2A', // Non radix 10 character.
|
||||||
|
'++A', // double sign.
|
||||||
|
'1__0', // 2 consecutive underscore
|
||||||
|
'+18446744073709551616', // u64 overflow by 1.
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou_common(v, max_u64) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for: "${v} -> ${err}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// performs numeric (bounds) tests over u8 type.
|
||||||
|
fn test_atou8() {
|
||||||
|
struct StrU8 {
|
||||||
|
str_value string
|
||||||
|
uint_value u8
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := [
|
||||||
|
StrU8{'0', 0},
|
||||||
|
StrU8{'+0', 0},
|
||||||
|
StrU8{'1', 1},
|
||||||
|
StrU8{'+39', 39},
|
||||||
|
StrU8{'1_23', 123},
|
||||||
|
StrU8{'00000006', 6},
|
||||||
|
StrU8{'+0_0_0_0_0_0_0_6', 6},
|
||||||
|
StrU8{'255', 255}, // max u8
|
||||||
|
]
|
||||||
|
|
||||||
|
// Check that extracted int value matches its string.
|
||||||
|
for v in ok {
|
||||||
|
// println('Parsing ${v.str_value} should equals ${v.int_value}')
|
||||||
|
assert strconv.atou8(v.str_value)! == v.uint_value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing of these values should fail !
|
||||||
|
ko := [
|
||||||
|
'256', // Overflow by one
|
||||||
|
'+65535', // overflow of superior type.
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou8(v) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for: "${v} -> ${err}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// performs numeric (bounds) tests over u16 type.
|
||||||
|
fn test_atou16() {
|
||||||
|
struct StrU16 {
|
||||||
|
str_value string
|
||||||
|
uint_value u16
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := [
|
||||||
|
StrU16{'0', 0},
|
||||||
|
StrU16{'+0', 0},
|
||||||
|
StrU16{'1', 1},
|
||||||
|
StrU16{'+16384', 16384},
|
||||||
|
StrU16{'1_23', 123},
|
||||||
|
StrU16{'00000006', 6},
|
||||||
|
StrU16{'+0_0_0_0_0_0_0_6', 6},
|
||||||
|
StrU16{'+3_2_7_6_8', 32768},
|
||||||
|
StrU16{'65535', 65535}, // max u16
|
||||||
|
]
|
||||||
|
|
||||||
|
// Check that extracted int value matches its string.
|
||||||
|
for v in ok {
|
||||||
|
// println('Parsing ${v.str_value} should equals ${v.int_value}')
|
||||||
|
assert strconv.atou16(v.str_value)! == v.uint_value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing of these values should fail !
|
||||||
|
ko := [
|
||||||
|
'65536', // Overflow by one
|
||||||
|
'+4294967295', // overflow of superior type.
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou16(v) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for: "${v} -> ${err}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// atou method acts actually with u32 boundary. In the future int may be mapped on 32/64bits
|
||||||
|
// depending on machine architecture. That's why we provide atou/atou32 code and tests.
|
||||||
|
fn test_atou() {
|
||||||
|
struct StrU32 {
|
||||||
|
str_value string
|
||||||
|
uint_value u32
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := [
|
||||||
|
StrU32{'0', 0},
|
||||||
|
StrU32{'+0', 0},
|
||||||
|
StrU32{'1', 1},
|
||||||
|
StrU32{'+3_14159', 314159},
|
||||||
|
StrU32{'1_00_1', 1001},
|
||||||
|
StrU32{'+1_024', 1024},
|
||||||
|
StrU32{'123_456_789', 123456789},
|
||||||
|
StrU32{'00000006', 6},
|
||||||
|
StrU32{'+0_0_0_0_0_0_0_6', 6},
|
||||||
|
StrU32{'2147483647', 2147483647},
|
||||||
|
StrU32{'+4294967295', 4294967295}, // max u32 bits
|
||||||
|
]
|
||||||
|
|
||||||
|
// Check that extracted int value matches its string.
|
||||||
|
for v in ok {
|
||||||
|
// println('Parsing ${v.str_value} should equals ${v.int_value}')
|
||||||
|
assert strconv.atou(v.str_value)! == v.uint_value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing of these values should fail !
|
||||||
|
ko := [
|
||||||
|
'4294967296', // Overflow by one
|
||||||
|
'+18446744073709551615', // overflow of superior type.
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou(v) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for: "${v} -> ${err}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// performs numeric (bounds) tests over u64 type.
|
||||||
|
fn test_atou32() {
|
||||||
|
struct StrU32 {
|
||||||
|
str_value string
|
||||||
|
uint_value u32
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := [
|
||||||
|
StrU32{'0', 0},
|
||||||
|
StrU32{'+0', 0},
|
||||||
|
StrU32{'1', 1},
|
||||||
|
StrU32{'+3_14159', 314159},
|
||||||
|
StrU32{'1_00_1', 1001},
|
||||||
|
StrU32{'+1_024', 1024},
|
||||||
|
StrU32{'123_456_789', 123456789},
|
||||||
|
StrU32{'00000006', 6},
|
||||||
|
StrU32{'+0_0_0_0_0_0_0_6', 6},
|
||||||
|
StrU32{'2147483647', 2147483647},
|
||||||
|
StrU32{'+4294967295', 4294967295}, // max u32 bits
|
||||||
|
]
|
||||||
|
|
||||||
|
// Check that extracted int value matches its string.
|
||||||
|
for v in ok {
|
||||||
|
// println('Parsing ${v.str_value} should equals ${v.int_value}')
|
||||||
|
assert strconv.atou32(v.str_value)! == v.uint_value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing of these values should fail !
|
||||||
|
ko := [
|
||||||
|
'4294967296', // Overflow by one
|
||||||
|
'+18446744073709551615', // overflow of superior type.
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou32(v) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for: "${v} -> ${err}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// performs numeric (bounds) tests over u64 type.
|
||||||
|
fn test_atou64() {
|
||||||
|
struct StrU64 {
|
||||||
|
str_value string
|
||||||
|
uint_value u64
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := [
|
||||||
|
StrU64{'0', 0},
|
||||||
|
StrU64{'+0', 0},
|
||||||
|
StrU64{'1', 1},
|
||||||
|
StrU64{'+3_14159', 314159},
|
||||||
|
StrU64{'1_00_1', 1001},
|
||||||
|
StrU64{'+1_024', 1024},
|
||||||
|
StrU64{'123_456_789', 123456789},
|
||||||
|
StrU64{'00000006', 6},
|
||||||
|
StrU64{'+0_0_0_0_0_0_0_6', 6},
|
||||||
|
StrU64{'2147483647', 2147483647},
|
||||||
|
StrU64{'+18446744073709551615', 18446744073709551615}, // max u64 bits
|
||||||
|
]
|
||||||
|
|
||||||
|
// Check that extracted int value matches its string.
|
||||||
|
for v in ok {
|
||||||
|
// println('Parsing ${v.str_value} should equals ${v.int_value}')
|
||||||
|
assert strconv.atou64(v.str_value)! == v.uint_value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing of these values should fail !
|
||||||
|
ko := [
|
||||||
|
'18446744073709551616', // Overflow by one
|
||||||
|
'+184467440214748364773709551615', // Large overflow .
|
||||||
|
]
|
||||||
|
|
||||||
|
for v in ko {
|
||||||
|
if r := strconv.atou64(v) {
|
||||||
|
// These conversions should fail so force assertion !
|
||||||
|
assert false, 'The string ${v} integer extraction should not succeed or be considered as valid ${r}).'
|
||||||
|
} else {
|
||||||
|
// println('Parsing fails as it should for: "${v} -> ${err}')
|
||||||
|
assert true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user