strconv: make atoi return ?int

pull/7444/head
Alexander Medvednikov 2020-12-21 08:35:24 +01:00
parent 2147d8785b
commit fc965b7d92
3 changed files with 66 additions and 48 deletions

View File

@ -1,15 +1,15 @@
module strconv module strconv
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. // Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license // Use of this source code is governed by an MIT license
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
// TODO: use optionals, or some way to return default with error. // TODO: use optionals, or some way to return default with error.
const ( const (
// int_size is the size in bits of an int or uint value. // int_size is the size in bits of an int or uint value.
// int_size = 32 << (~u32(0) >> 63) // int_size = 32 << (~u32(0) >> 63)
// max_u64 = u64(u64(1 << 63) - 1) // max_u64 = u64(u64(1 << 63) - 1)
int_size = 32 int_size = 32
max_u64 = u64(18446744073709551615) // as u64 // use this until we add support max_u64 = u64(18446744073709551615) // as u64 // use this until we add support
) )
pub fn byte_to_lower(c byte) byte { pub fn byte_to_lower(c byte) byte {
@ -41,20 +41,17 @@ pub fn common_parse_uint2(s string, _base int, _bit_size int) (u64, int) {
mut start_index := 0 mut start_index := 0
if 2 <= base && base <= 36 { if 2 <= base && base <= 36 {
// valid base; nothing to do // valid base; nothing to do
} } else if base == 0 {
else if base == 0 {
// Look for octal, hex prefix. // Look for octal, hex prefix.
base = 10 base = 10
if s[0] == `0` { if s[0] == `0` {
if s.len >= 3 && byte_to_lower(s[1]) == `b` { if s.len >= 3 && byte_to_lower(s[1]) == `b` {
base = 2 base = 2
start_index += 2 start_index += 2
} } else if s.len >= 3 && byte_to_lower(s[1]) == `o` {
else if s.len >= 3 && byte_to_lower(s[1]) == `o` {
base = 8 base = 8
start_index += 2 start_index += 2
} } else if s.len >= 3 && byte_to_lower(s[1]) == `x` {
else if s.len >= 3 && byte_to_lower(s[1]) == `x` {
base = 16 base = 16
start_index += 2 start_index += 2
} }
@ -62,28 +59,25 @@ pub fn common_parse_uint2(s string, _base int, _bit_size int) (u64, int) {
else if s.len >= 2 && (s[1] >= `0` && s[1] <= `9`) { else if s.len >= 2 && (s[1] >= `0` && s[1] <= `9`) {
base = 10 base = 10
start_index++ start_index++
} } else {
else {
base = 8 base = 8
start_index++ start_index++
} }
} }
} } else {
else {
// return error('parse_uint: base error $s - $base') // return error('parse_uint: base error $s - $base')
return u64(0), -1 return u64(0), -1
} }
if bit_size == 0 { if bit_size == 0 {
bit_size = int_size bit_size = int_size
} } else if bit_size < 0 || bit_size > 64 {
else if bit_size < 0 || bit_size > 64 {
// return error('parse_uint: bitsize error $s - $bit_size') // return error('parse_uint: bitsize error $s - $bit_size')
return u64(0), -2 return u64(0), -2
} }
// Cutoff is the smallest number such that cutoff*base > maxUint64. // Cutoff is the smallest number such that cutoff*base > maxUint64.
// Use compile-time constants for common cases. // Use compile-time constants for common cases.
cutoff := max_u64 / u64(base) + u64(1) cutoff := max_u64 / u64(base) + u64(1)
max_val := if bit_size == 64 { max_u64 } else { (u64(1)<<u64(bit_size)) - u64(1) } max_val := if bit_size == 64 { max_u64 } else { (u64(1) << u64(bit_size)) - u64(1) }
mut n := u64(0) mut n := u64(0)
for i in start_index .. s.len { for i in start_index .. s.len {
c := s[i] c := s[i]
@ -92,14 +86,11 @@ pub fn common_parse_uint2(s string, _base int, _bit_size int) (u64, int) {
if c == `_` && base0 { if c == `_` && base0 {
// underscore_ok already called // underscore_ok already called
continue continue
} } else if `0` <= c && c <= `9` {
else if `0` <= c && c <= `9` {
d = c - `0` d = c - `0`
} } else if `a` <= cl && cl <= `z` {
else if `a` <= cl && cl <= `z` {
d = cl - `a` + 10 d = cl - `a` + 10
} } else {
else {
return n, i + 1 return n, i + 1
} }
if d >= byte(base) { if d >= byte(base) {
@ -140,8 +131,7 @@ pub fn common_parse_int(_s string, base int, _bit_size int, error_on_non_digit b
mut neg := false mut neg := false
if s[0] == `+` { if s[0] == `+` {
s = s[1..] s = s[1..]
} } else if s[0] == `-` {
else if s[0] == `-` {
neg = true neg = true
s = s[1..] s = s[1..]
} }
@ -157,7 +147,7 @@ pub fn common_parse_int(_s string, base int, _bit_size int, error_on_non_digit b
bit_size = int_size bit_size = int_size
} }
// TODO: check should u64(bit_size-1) be size of int (32)? // TODO: check should u64(bit_size-1) be size of int (32)?
cutoff := u64(1)<<u64(bit_size - 1) cutoff := u64(1) << u64(bit_size - 1)
if !neg && un >= cutoff { if !neg && un >= cutoff {
// return error('parse_int: range error $s0') // return error('parse_int: range error $s0')
return i64(cutoff - u64(1)) return i64(cutoff - u64(1))
@ -166,7 +156,11 @@ pub fn common_parse_int(_s string, base int, _bit_size int, error_on_non_digit b
// return error('parse_int: range error $s0') // return error('parse_int: range error $s0')
return -i64(cutoff) return -i64(cutoff)
} }
return if neg { -i64(un) } else { i64(un) } return if neg {
-i64(un)
} else {
i64(un)
}
} }
// parse_int interprets a string s in the given base (0, 2 to 36) and // parse_int interprets a string s in the given base (0, 2 to 36) and
@ -186,15 +180,20 @@ pub fn parse_int(_s string, base int, _bit_size int) i64 {
} }
// atoi is equivalent to parse_int(s, 10, 0), converted to type int. // atoi is equivalent to parse_int(s, 10, 0), converted to type int.
pub fn atoi(s string) int { pub fn atoi(s string) ?int {
if (int_size == 32 && (0 < s.len && s.len < 10)) || (int_size == 64 && (0 < s.len && s.len < 19)) { if s == '' {
return error('strconv.atoi: parsing "$s": invalid syntax ')
}
if (int_size == 32 && (0 < s.len &&
s.len < 10)) ||
(int_size == 64 && (0 < s.len && s.len < 19)) {
// Fast path for small integers that fit int type. // Fast path for small integers that fit int type.
mut start_idx := 0 mut start_idx := 0
if s[0] == `-` || s[0] == `+` { if s[0] == `-` || s[0] == `+` {
start_idx++ start_idx++
if s.len - start_idx < 1 { if s.len - start_idx < 1 {
// return 0, &NumError{fnAtoi, s0, ErrSyntax} // return 0, &NumError{fnAtoi, s0, ErrSyntax}
return 0 return error('strconv.atoi: parsing "$s": invalid syntax ')
} }
} }
mut n := 0 mut n := 0
@ -202,11 +201,15 @@ pub fn atoi(s string) int {
ch := s[i] - `0` ch := s[i] - `0`
if ch > 9 { if ch > 9 {
// return 0, &NumError{fnAtoi, s0, ErrSyntax} // return 0, &NumError{fnAtoi, s0, ErrSyntax}
return 0 return error('strconv.atoi: parsing "$s": invalid syntax ')
} }
n = n * 10 + int(ch) n = n * 10 + int(ch)
} }
return if s[0] == `-` { -n } else { n } return if s[0] == `-` {
-n
} else {
n
}
} }
// Slow path for invalid, big, or underscored integers. // Slow path for invalid, big, or underscored integers.
int64 := parse_int(s, 10, 0) int64 := parse_int(s, 10, 0)
@ -230,7 +233,8 @@ fn underscore_ok(s string) bool {
} }
// Optional base prefix. // Optional base prefix.
mut hex := false mut hex := false
if s.len - i >= 2 && s[i] == `0` && (byte_to_lower(s[i + 1]) == `b` || byte_to_lower(s[i + 1]) == `o` || byte_to_lower(s[i + 1]) == `x`) { if s.len - i >= 2 && s[i] == `0` &&
(byte_to_lower(s[i + 1]) == `b` || byte_to_lower(s[i + 1]) == `o` || byte_to_lower(s[i + 1]) == `x`) {
saw = `0` // base prefix counts as a digit for "underscore as digit separator" saw = `0` // base prefix counts as a digit for "underscore as digit separator"
hex = byte_to_lower(s[i + 1]) == `x` hex = byte_to_lower(s[i + 1]) == `x`
i += 2 i += 2
@ -238,7 +242,8 @@ fn underscore_ok(s string) bool {
// Number proper. // Number proper.
for ; i < s.len; i++ { for ; i < s.len; i++ {
// Digits are always okay. // Digits are always okay.
if (`0` <= s[i] && s[i] <= `9`) || (hex && `a` <= byte_to_lower(s[i]) && byte_to_lower(s[i]) <= `f`) { if (`0` <= s[i] && s[i] <= `9`) ||
(hex && `a` <= byte_to_lower(s[i]) && byte_to_lower(s[i]) <= `f`) {
saw = `0` saw = `0`
continue continue
} }
@ -259,4 +264,3 @@ fn underscore_ok(s string) bool {
} }
return saw != `_` return saw != `_`
} }

View File

@ -1,11 +1,31 @@
import strconv import strconv
fn test_atoi() { fn test_atoi() {
assert strconv.atoi('16') == 16 if x := strconv.atoi('16') {
assert strconv.atoi('+16') == 16 assert x == 16
assert strconv.atoi('-16') == -16 } else {
assert strconv.atoi('str') == 0 assert false
assert strconv.atoi('') == 0 }
if x := strconv.atoi('+16') {
assert x == 16
} else {
assert false
}
if x := strconv.atoi('-16') {
assert x == -16
} else {
assert false
}
if x := strconv.atoi('str') {
assert false
} else {
assert true
}
if x := strconv.atoi('') {
assert false
} else {
assert true
}
} }
fn test_parse_int() { fn test_parse_int() {
@ -31,31 +51,24 @@ fn test_common_parse_uint2() {
mut result, mut error := strconv.common_parse_uint2('1', 10, 8) mut result, mut error := strconv.common_parse_uint2('1', 10, 8)
assert result == 1 assert result == 1
assert error == 0 assert error == 0
result, error = strconv.common_parse_uint2('123', 10, 8) result, error = strconv.common_parse_uint2('123', 10, 8)
assert result == 123 assert result == 123
assert error == 0 assert error == 0
result, error = strconv.common_parse_uint2('123', 10, 65) result, error = strconv.common_parse_uint2('123', 10, 65)
assert result == 0 assert result == 0
assert error == -2 assert error == -2
result, error = strconv.common_parse_uint2('123', 10, -1) result, error = strconv.common_parse_uint2('123', 10, -1)
assert result == 0 assert result == 0
assert error == -2 assert error == -2
result, error = strconv.common_parse_uint2('', 10, 8) result, error = strconv.common_parse_uint2('', 10, 8)
assert result == 0 assert result == 0
assert error == 1 assert error == 1
result, error = strconv.common_parse_uint2('1a', 10, 8) result, error = strconv.common_parse_uint2('1a', 10, 8)
assert result == 1 assert result == 1
assert error == 2 assert error == 2
result, error = strconv.common_parse_uint2('12a', 10, 8) result, error = strconv.common_parse_uint2('12a', 10, 8)
assert result == 12 assert result == 12
assert error == 3 assert error == 3
result, error = strconv.common_parse_uint2('123a', 10, 8) result, error = strconv.common_parse_uint2('123a', 10, 8)
assert result == 123 assert result == 123
assert error == 4 assert error == 4

View File

@ -409,6 +409,7 @@ pub fn parse_args(args []string) (&Preferences, string) {
} }
} }
} }
// res.use_cache = true
if command != 'doc' && res.out_name.ends_with('.v') { if command != 'doc' && res.out_name.ends_with('.v') {
eprintln('Cannot save output binary in a .v file.') eprintln('Cannot save output binary in a .v file.')
exit(1) exit(1)