[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")]