[opentitantool] Add a proper int parser.
The default integer parsers in Rust are deficient: there is no equivalent
of `strtol` or `strtoul` with a base of 0 (ie: which autodetects the integer
base using classic C-language prefixes).
1. Add a ParseInt trait which implements proper integer parsing for use in
developer and low-level tooling.
2. Replace the prior use of `parse_u16` with the ParseInt trait.
Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/src/util/mod.rs b/sw/host/opentitanlib/src/util/mod.rs
index 643423d..e6c4ba9 100644
--- a/sw/host/opentitanlib/src/util/mod.rs
+++ b/sw/host/opentitanlib/src/util/mod.rs
@@ -2,15 +2,6 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
-use std::num::ParseIntError;
-
pub mod bitfield;
pub mod file;
-
-pub fn parse_u16(src: &str) -> Result<u16, ParseIntError> {
- if let Some(val) = src.strip_prefix("0x") {
- u16::from_str_radix(val, 16)
- } else {
- u16::from_str_radix(src, 10)
- }
-}
+pub mod parse_int;
diff --git a/sw/host/opentitanlib/src/util/parse_int.rs b/sw/host/opentitanlib/src/util/parse_int.rs
new file mode 100644
index 0000000..beae994
--- /dev/null
+++ b/sw/host/opentitanlib/src/util/parse_int.rs
@@ -0,0 +1,243 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use std::num::ParseIntError;
+
+/// Trait for parsing integers.
+///
+/// Strings beginning with the common and classic prefixes `0x`, `0o`, `0b` or `0` are parsed
+/// as hex, octal, binary, and octal (classic). Anything else is parsed as decimal.
+/// A leading `+` or `-` is permitted. Any string parsed by `strtol(3)` or `strtoul(3)`
+/// will be parsed successfully.
+pub trait ParseInt: Sized {
+ fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError>;
+
+ fn from_str(src: &str) -> Result<Self, ParseIntError> {
+ let (val, negative) = if let Some(v) = src.strip_prefix("-") {
+ (v, true)
+ } else if let Some(v) = src.strip_prefix("+") {
+ (v, false)
+ } else {
+ (src, false)
+ };
+
+ let (radix, digits) = match val.get(0..2) {
+ Some("0x") | Some("0X") => (16, &val[2..]),
+ Some("0o") | Some("0O") => (8, &val[2..]),
+ Some("0b") | Some("0B") => (2, &val[2..]),
+ _ if val.starts_with("0") => (8, val),
+ _ => (10, val),
+ };
+
+ if negative {
+ let mut digits = digits.to_string();
+ digits.insert(0, '-');
+ Self::from_str_radix(&digits, radix)
+ } else {
+ Self::from_str_radix(digits, radix)
+ }
+ }
+}
+
+macro_rules! impl_parse_int {
+ ($ty:ident) => {
+ impl ParseInt for $ty {
+ fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError> {
+ $ty::from_str_radix(src, radix)
+ }
+ }
+ };
+}
+
+impl_parse_int!(i8);
+impl_parse_int!(u8);
+impl_parse_int!(i16);
+impl_parse_int!(u16);
+impl_parse_int!(i32);
+impl_parse_int!(u32);
+impl_parse_int!(i64);
+impl_parse_int!(u64);
+impl_parse_int!(isize);
+impl_parse_int!(usize);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_i8() {
+ assert_eq!(i8::from_str("0"), Ok(0));
+ assert_eq!(i8::from_str("-1"), Ok(-1));
+ assert_eq!(i8::from_str("+100"), Ok(100));
+ assert_eq!(i8::from_str("-128"), Ok(-128));
+ assert_eq!(i8::from_str("0x10"), Ok(16));
+ assert_eq!(i8::from_str("-0x10"), Ok(-16));
+ assert_eq!(i8::from_str("0o10"), Ok(8));
+ assert_eq!(i8::from_str("-0o10"), Ok(-8));
+ assert_eq!(i8::from_str("0b10"), Ok(2));
+ assert_eq!(i8::from_str("-0b10"), Ok(-2));
+ assert_eq!(i8::from_str("012"), Ok(10));
+ assert!(i8::from_str("128").is_err());
+ assert!(i8::from_str("-129").is_err());
+ }
+
+ #[test]
+ fn test_u8() {
+ assert_eq!(u8::from_str("0"), Ok(0));
+ assert_eq!(u8::from_str("+100"), Ok(100));
+ assert_eq!(u8::from_str("128"), Ok(128));
+ assert_eq!(u8::from_str("255"), Ok(255));
+ assert_eq!(u8::from_str("0x10"), Ok(16));
+ assert_eq!(u8::from_str("0xFF"), Ok(255));
+ assert_eq!(u8::from_str("0o10"), Ok(8));
+ assert_eq!(u8::from_str("0b10000000"), Ok(128));
+ assert_eq!(u8::from_str("012"), Ok(10));
+ assert!(u8::from_str("-1").is_err());
+ assert!(u8::from_str("256").is_err());
+ }
+
+ #[test]
+ fn test_i16() {
+ assert_eq!(i16::from_str("0"), Ok(0));
+ assert_eq!(i16::from_str("-1"), Ok(-1));
+ assert_eq!(i16::from_str("+32767"), Ok(32767));
+ assert_eq!(i16::from_str("-32768"), Ok(-32768));
+ assert_eq!(i16::from_str("0x3fff"), Ok(16383));
+ assert_eq!(i16::from_str("-0x10"), Ok(-16));
+ assert_eq!(i16::from_str("0o10"), Ok(8));
+ assert_eq!(i16::from_str("-0o10"), Ok(-8));
+ assert_eq!(i16::from_str("0b10"), Ok(2));
+ assert_eq!(i16::from_str("-0b10"), Ok(-2));
+ assert_eq!(i16::from_str("012"), Ok(10));
+ assert!(i16::from_str("32768").is_err());
+ assert!(i16::from_str("-32769").is_err());
+ }
+
+ #[test]
+ fn test_u16() {
+ assert_eq!(u16::from_str("0"), Ok(0));
+ assert_eq!(u16::from_str("+100"), Ok(100));
+ assert_eq!(u16::from_str("32768"), Ok(32768));
+ assert_eq!(u16::from_str("65535"), Ok(65535));
+ assert_eq!(u16::from_str("0x10"), Ok(16));
+ assert_eq!(u16::from_str("0xFF"), Ok(255));
+ assert_eq!(u16::from_str("0o10"), Ok(8));
+ assert_eq!(u16::from_str("0b1000000000000000"), Ok(32768));
+ assert_eq!(u16::from_str("012"), Ok(10));
+ assert!(u16::from_str("-1").is_err());
+ assert!(u16::from_str("65536").is_err());
+ }
+
+ #[test]
+ fn test_i32() {
+ assert_eq!(i32::from_str("0"), Ok(0));
+ assert_eq!(i32::from_str("-1"), Ok(-1));
+ assert_eq!(i32::from_str("+2147483647"), Ok(2147483647));
+ assert_eq!(i32::from_str("-2147483648"), Ok(-2147483648));
+ assert_eq!(i32::from_str("0x7fffffff"), Ok(2147483647));
+ assert_eq!(i32::from_str("-0x10"), Ok(-16));
+ assert_eq!(i32::from_str("0o10"), Ok(8));
+ assert_eq!(i32::from_str("-0o10"), Ok(-8));
+ assert_eq!(i32::from_str("0b10"), Ok(2));
+ assert_eq!(i32::from_str("-0b10"), Ok(-2));
+ assert_eq!(i32::from_str("012"), Ok(10));
+ assert!(i32::from_str("2147483648").is_err());
+ assert!(i32::from_str("-2147483649").is_err());
+ }
+
+ #[test]
+ fn test_u32() {
+ assert_eq!(u32::from_str("0"), Ok(0));
+ assert_eq!(u32::from_str("+100"), Ok(100));
+ assert_eq!(u32::from_str("2147483648"), Ok(2147483648));
+ assert_eq!(u32::from_str("4294967295"), Ok(4294967295));
+ assert_eq!(u32::from_str("0x10"), Ok(16));
+ assert_eq!(u32::from_str("0xFF"), Ok(255));
+ assert_eq!(u32::from_str("0o10"), Ok(8));
+ assert_eq!(u32::from_str("012"), Ok(10));
+ assert_eq!(
+ u32::from_str("0b10000000000000000000000000000000"),
+ Ok(2147483648)
+ );
+ assert!(u32::from_str("-1").is_err());
+ assert!(u32::from_str("4294967296").is_err());
+ }
+
+ #[test]
+ fn test_i64() {
+ assert_eq!(i64::from_str("0"), Ok(0));
+ assert_eq!(i64::from_str("-1"), Ok(-1));
+ assert_eq!(
+ i64::from_str("+9223372036854775807"),
+ Ok(9223372036854775807)
+ );
+ assert_eq!(
+ i64::from_str("-9223372036854775808"),
+ Ok(-9223372036854775808)
+ );
+ assert_eq!(i64::from_str("0x7fffffff"), Ok(2147483647));
+ assert_eq!(i64::from_str("-0x10"), Ok(-16));
+ assert_eq!(i64::from_str("0o10"), Ok(8));
+ assert_eq!(i64::from_str("-0o10"), Ok(-8));
+ assert_eq!(i64::from_str("0b10"), Ok(2));
+ assert_eq!(i64::from_str("-0b10"), Ok(-2));
+ assert_eq!(i64::from_str("012"), Ok(10));
+ assert!(i64::from_str("9223372036854775808").is_err());
+ assert!(i64::from_str("-9223372036854775809").is_err());
+ }
+
+ #[test]
+ fn test_u64() {
+ assert_eq!(u64::from_str("0"), Ok(0));
+ assert_eq!(u64::from_str("+100"), Ok(100));
+ assert_eq!(
+ u64::from_str("+9223372036854775808"),
+ Ok(9223372036854775808)
+ );
+ assert_eq!(
+ u64::from_str("18446744073709551615"),
+ Ok(18446744073709551615)
+ );
+ assert_eq!(u64::from_str("0x10"), Ok(16));
+ assert_eq!(u64::from_str("0xFF"), Ok(255));
+ assert_eq!(u64::from_str("0o10"), Ok(8));
+ assert_eq!(
+ u64::from_str("0b10000000000000000000000000000000"),
+ Ok(2147483648)
+ );
+ assert_eq!(u64::from_str("012"), Ok(10));
+ assert!(u64::from_str("-1").is_err());
+ assert!(u64::from_str("18446744073709551616").is_err());
+ }
+
+ #[test]
+ fn test_isize() {
+ // Since isize's size is target defined, we don't bother with
+ // trying to test large values and assume the tests for the
+ // other integer types will cover the values for isize.
+ assert_eq!(isize::from_str("0"), Ok(0));
+ assert_eq!(isize::from_str("-1"), Ok(-1));
+ assert_eq!(isize::from_str("0x7f"), Ok(127));
+ assert_eq!(isize::from_str("-0x10"), Ok(-16));
+ assert_eq!(isize::from_str("0o10"), Ok(8));
+ assert_eq!(isize::from_str("-0o10"), Ok(-8));
+ assert_eq!(isize::from_str("0b10"), Ok(2));
+ assert_eq!(isize::from_str("-0b10"), Ok(-2));
+ assert_eq!(isize::from_str("012"), Ok(10));
+ }
+
+ #[test]
+ fn test_usize() {
+ // Since usize's size is target defined, we don't bother with
+ // trying to test large values and assume the tests for the
+ // other integer types will cover the values for usize.
+ assert_eq!(usize::from_str("0"), Ok(0));
+ assert_eq!(usize::from_str("+100"), Ok(100));
+ assert_eq!(usize::from_str("0x10"), Ok(16));
+ assert_eq!(usize::from_str("0xFF"), Ok(255));
+ assert_eq!(usize::from_str("0o10"), Ok(8));
+ assert_eq!(usize::from_str("012"), Ok(10));
+ assert!(usize::from_str("-1").is_err());
+ }
+}
diff --git a/sw/host/opentitantool/src/backend/mod.rs b/sw/host/opentitantool/src/backend/mod.rs
index c9b1f5f..596957f 100644
--- a/sw/host/opentitantool/src/backend/mod.rs
+++ b/sw/host/opentitantool/src/backend/mod.rs
@@ -7,7 +7,7 @@
use thiserror::Error;
use opentitanlib::transport::{EmptyTransport, Transport};
-use opentitanlib::util::parse_u16;
+use opentitanlib::util::parse_int::ParseInt;
pub mod ultradebug;
pub mod verilator;
@@ -17,10 +17,10 @@
#[structopt(long, default_value, help = "Name of the debug interface")]
interface: String,
- #[structopt(long, parse(try_from_str = parse_u16),
+ #[structopt(long, parse(try_from_str = u16::from_str),
help="USB Vendor ID of the interface")]
usb_vid: Option<u16>,
- #[structopt(long, parse(try_from_str = parse_u16),
+ #[structopt(long, parse(try_from_str = u16::from_str),
help="USB Product ID of the interface")]
usb_pid: Option<u16>,
#[structopt(long, help = "USB serial number of the interface")]