mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 21:33:09 +00:00
Introduce fish_wcstoi_partial
fish_wcstoi_partial is like fish_wcstoi: it converts from a string to an int optionally inferring the radix. fish_wcstoi_partial also returns the number of characters consumed.
This commit is contained in:
parent
7729d3206a
commit
dc8aab3f52
7 changed files with 122 additions and 38 deletions
|
@ -99,12 +99,12 @@ pub fn bg(parser: &mut parser_t, streams: &mut io_streams_t, args: &mut [&wstr])
|
|||
let mut retval = STATUS_CMD_OK;
|
||||
let pids: Vec<i64> = args[opts.optind..]
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
fish_wcstoi(arg.chars()).unwrap_or_else(|_| {
|
||||
.map(|&arg| {
|
||||
fish_wcstoi(arg).unwrap_or_else(|_| {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: '%ls' is not a valid job specifier\n",
|
||||
cmd,
|
||||
*arg
|
||||
arg
|
||||
));
|
||||
retval = STATUS_INVALID_ARGS;
|
||||
0
|
||||
|
|
|
@ -73,7 +73,7 @@ pub fn random(
|
|||
cmd: &wstr,
|
||||
num: &wstr,
|
||||
) -> Result<T, wutil::Error> {
|
||||
let res = fish_wcstoi_radix_all(num.chars(), None, true);
|
||||
let res = fish_wcstoi_radix_all(num, None, true);
|
||||
if res.is_err() {
|
||||
streams
|
||||
.err
|
||||
|
|
|
@ -117,7 +117,7 @@ pub fn parse_return_value(
|
|||
if optind == args.len() {
|
||||
Ok(parser.get_last_status().into())
|
||||
} else {
|
||||
match fish_wcstoi(args[optind].chars()) {
|
||||
match fish_wcstoi(args[optind]) {
|
||||
Ok(i) => Ok(i),
|
||||
Err(_e) => {
|
||||
streams
|
||||
|
|
|
@ -196,7 +196,7 @@ pub fn wait(
|
|||
for i in w.woptind..argc {
|
||||
if iswnumeric(argv[i]) {
|
||||
// argument is pid
|
||||
let mpid: Result<pid_t, wutil::Error> = fish_wcstoi(argv[i].chars());
|
||||
let mpid: Result<pid_t, wutil::Error> = fish_wcstoi(argv[i]);
|
||||
if mpid.is_err() || mpid.unwrap() <= 0 {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: '%ls' is not a valid process id\n",
|
||||
|
|
|
@ -112,7 +112,7 @@ impl RedirectionSpec {
|
|||
|
||||
/// Attempt to parse target as an fd.
|
||||
pub fn get_target_as_fd(&self) -> Option<RawFd> {
|
||||
fish_wcstoi(self.target.as_char_slice().iter().copied()).ok()
|
||||
fish_wcstoi(&self.target).ok()
|
||||
}
|
||||
fn get_target_as_fd_ffi(&self) -> SharedPtr<i32> {
|
||||
match self.get_target_as_fd() {
|
||||
|
|
|
@ -116,6 +116,22 @@ impl<'a> IntoCharIter for &'a WString {
|
|||
}
|
||||
}
|
||||
|
||||
// Also support `str.chars()` itself.
|
||||
impl<'a> IntoCharIter for std::str::Chars<'a> {
|
||||
type Iter = Self;
|
||||
fn chars(self) -> Self::Iter {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Also support `wstr.chars()` itself.
|
||||
impl<'a> IntoCharIter for CharsUtf32<'a> {
|
||||
type Iter = Self;
|
||||
fn chars(self) -> Self::Iter {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// \return true if \p prefix is a prefix of \p contents.
|
||||
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
|
||||
where
|
||||
|
|
|
@ -1,22 +1,39 @@
|
|||
pub use super::errors::Error;
|
||||
use crate::wchar::IntoCharIter;
|
||||
use num_traits::{NumCast, PrimInt};
|
||||
use std::iter::Peekable;
|
||||
use std::iter::{Fuse, Peekable};
|
||||
use std::result::Result;
|
||||
|
||||
struct ParseResult {
|
||||
result: u64,
|
||||
negative: bool,
|
||||
consumed_all: bool,
|
||||
consumed: usize,
|
||||
}
|
||||
|
||||
/// Helper to get the current char, or \0.
|
||||
fn current<Chars>(chars: &mut Peekable<Chars>) -> char
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
{
|
||||
match chars.peek() {
|
||||
Some(c) => *c,
|
||||
None => '\0',
|
||||
struct CharsIterator<Iter: Iterator<Item = char>> {
|
||||
chars: Peekable<Fuse<Iter>>,
|
||||
consumed: usize,
|
||||
}
|
||||
|
||||
impl<Iter: Iterator<Item = char>> CharsIterator<Iter> {
|
||||
/// Get the current char, or \0.
|
||||
fn current(&mut self) -> char {
|
||||
self.peek().unwrap_or('\0')
|
||||
}
|
||||
|
||||
/// Get the current char, or None.
|
||||
fn peek(&mut self) -> Option<char> {
|
||||
self.chars.peek().copied()
|
||||
}
|
||||
|
||||
/// Get the next char, incrementing self.consumed.
|
||||
fn next(&mut self) -> Option<char> {
|
||||
let res = self.chars.next();
|
||||
if res.is_some() {
|
||||
self.consumed += 1;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,17 +43,22 @@ where
|
|||
/// - Leading 0 means 8.
|
||||
/// - Otherwise 10.
|
||||
/// The parse result contains the number as a u64, and whether it was negative.
|
||||
fn fish_parse_radix<Chars>(ichars: Chars, mradix: Option<u32>) -> Result<ParseResult, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
{
|
||||
fn fish_parse_radix<Iter: Iterator<Item = char>>(
|
||||
iter: Iter,
|
||||
mradix: Option<u32>,
|
||||
) -> Result<ParseResult, Error> {
|
||||
if let Some(r) = mradix {
|
||||
assert!((2..=36).contains(&r), "fish_parse_radix: invalid radix {r}");
|
||||
}
|
||||
let chars = &mut ichars.peekable();
|
||||
|
||||
// Construct a CharsIterator to keep track of how many we consume.
|
||||
let mut chars = CharsIterator {
|
||||
chars: iter.fuse().peekable(),
|
||||
consumed: 0,
|
||||
};
|
||||
|
||||
// Skip leading whitespace.
|
||||
while current(chars).is_whitespace() {
|
||||
while chars.current().is_whitespace() {
|
||||
chars.next();
|
||||
}
|
||||
|
||||
|
@ -46,9 +68,9 @@ where
|
|||
|
||||
// Consume leading +/-.
|
||||
let mut negative;
|
||||
match current(chars) {
|
||||
match chars.current() {
|
||||
'-' | '+' => {
|
||||
negative = current(chars) == '-';
|
||||
negative = chars.current() == '-';
|
||||
chars.next();
|
||||
}
|
||||
_ => negative = false,
|
||||
|
@ -57,9 +79,9 @@ where
|
|||
// Determine the radix.
|
||||
let radix = if let Some(radix) = mradix {
|
||||
radix
|
||||
} else if current(chars) == '0' {
|
||||
} else if chars.current() == '0' {
|
||||
chars.next();
|
||||
match current(chars) {
|
||||
match chars.current() {
|
||||
'x' | 'X' => {
|
||||
chars.next();
|
||||
16
|
||||
|
@ -71,6 +93,7 @@ where
|
|||
result: 0,
|
||||
negative: false,
|
||||
consumed_all: chars.peek().is_none(),
|
||||
consumed: chars.consumed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -79,19 +102,19 @@ where
|
|||
};
|
||||
|
||||
// Compute as u64.
|
||||
let mut consumed1 = false;
|
||||
let start_consumed = chars.consumed;
|
||||
let mut result: u64 = 0;
|
||||
while let Some(digit) = current(chars).to_digit(radix) {
|
||||
while let Some(digit) = chars.current().to_digit(radix) {
|
||||
result = result
|
||||
.checked_mul(radix as u64)
|
||||
.and_then(|r| r.checked_add(digit as u64))
|
||||
.ok_or(Error::Overflow)?;
|
||||
chars.next();
|
||||
consumed1 = true;
|
||||
}
|
||||
|
||||
// Did we consume at least one char?
|
||||
if !consumed1 {
|
||||
// Did we consume at least one char after the prefix?
|
||||
let consumed = chars.consumed;
|
||||
if consumed == start_consumed {
|
||||
return Err(Error::InvalidChar);
|
||||
}
|
||||
|
||||
|
@ -104,6 +127,7 @@ where
|
|||
result,
|
||||
negative,
|
||||
consumed_all,
|
||||
consumed,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -112,6 +136,7 @@ fn fish_wcstoi_impl<Int, Chars>(
|
|||
src: Chars,
|
||||
mradix: Option<u32>,
|
||||
consume_all: bool,
|
||||
out_consumed: &mut usize,
|
||||
) -> Result<Int, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
|
@ -125,8 +150,9 @@ where
|
|||
result,
|
||||
negative,
|
||||
consumed_all,
|
||||
..
|
||||
consumed,
|
||||
} = fish_parse_radix(src, mradix)?;
|
||||
*out_consumed = consumed;
|
||||
|
||||
if !signed && negative {
|
||||
Err(Error::InvalidChar)
|
||||
|
@ -158,20 +184,20 @@ where
|
|||
/// - Leading + is supported.
|
||||
pub fn fish_wcstoi<Int, Chars>(src: Chars) -> Result<Int, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
Chars: IntoCharIter,
|
||||
Int: PrimInt,
|
||||
{
|
||||
fish_wcstoi_impl(src, None, false)
|
||||
fish_wcstoi_impl(src.chars(), None, false, &mut 0)
|
||||
}
|
||||
|
||||
/// Convert the given wide string to an integer using the given radix.
|
||||
/// Leading whitespace is skipped.
|
||||
pub fn fish_wcstoi_radix<Int, Chars>(src: Chars, radix: u32) -> Result<Int, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
Chars: IntoCharIter,
|
||||
Int: PrimInt,
|
||||
{
|
||||
fish_wcstoi_impl(src, Some(radix), false)
|
||||
fish_wcstoi_impl(src.chars(), Some(radix), false, &mut 0)
|
||||
}
|
||||
|
||||
pub fn fish_wcstoi_radix_all<Int, Chars>(
|
||||
|
@ -180,10 +206,24 @@ pub fn fish_wcstoi_radix_all<Int, Chars>(
|
|||
consume_all: bool,
|
||||
) -> Result<Int, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
Chars: IntoCharIter,
|
||||
Int: PrimInt,
|
||||
{
|
||||
fish_wcstoi_impl(src, radix, consume_all)
|
||||
fish_wcstoi_impl(src.chars(), radix, consume_all, &mut 0)
|
||||
}
|
||||
|
||||
/// Convert the given wide string to an integer.
|
||||
/// The semantics here match wcstol():
|
||||
/// - Leading whitespace is skipped.
|
||||
/// - 0 means octal, 0x means hex
|
||||
/// - Leading + is supported.
|
||||
/// The number of consumed characters is returned in out_consumed.
|
||||
pub fn fish_wcstoi_partial<Int, Chars>(src: Chars, out_consumed: &mut usize) -> Result<Int, Error>
|
||||
where
|
||||
Chars: IntoCharIter,
|
||||
Int: PrimInt,
|
||||
{
|
||||
fish_wcstoi_impl(src.chars(), None, false, out_consumed)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -205,8 +245,14 @@ mod tests {
|
|||
assert_eq!(run1("0"), Ok(0));
|
||||
assert_eq!(run1("-0"), Ok(0));
|
||||
assert_eq!(run1("+0"), Ok(0));
|
||||
assert_eq!(run1("+00"), Ok(0));
|
||||
assert_eq!(run1("-00"), Ok(0));
|
||||
assert_eq!(run1("+0x00"), Ok(0));
|
||||
assert_eq!(run1("-0x00"), Ok(0));
|
||||
assert_eq!(run1("+-0"), Err(Error::InvalidChar));
|
||||
assert_eq!(run1("-+0"), Err(Error::InvalidChar));
|
||||
assert_eq!(run1("5"), Ok(5));
|
||||
assert_eq!(run1("-5"), Ok(-5));
|
||||
assert_eq!(run1("123"), Ok(123));
|
||||
assert_eq!(run1("+123"), Ok(123));
|
||||
assert_eq!(run1("-123"), Ok(-123));
|
||||
|
@ -236,4 +282,26 @@ mod tests {
|
|||
test_min_max(std::u32::MIN, std::u32::MAX);
|
||||
test_min_max(std::u64::MIN, std::u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial() {
|
||||
let run1 = |s: &str| -> (i32, usize) {
|
||||
let mut consumed = 0;
|
||||
let res =
|
||||
fish_wcstoi_partial(s.chars(), &mut consumed).expect("Should have parsed an int");
|
||||
(res, consumed)
|
||||
};
|
||||
|
||||
assert_eq!(run1("0"), (0, 1));
|
||||
assert_eq!(run1("-0"), (0, 2));
|
||||
assert_eq!(run1(" -1 "), (-1, 3));
|
||||
assert_eq!(run1(" +1 "), (1, 3));
|
||||
assert_eq!(run1(" 345 "), (345, 5));
|
||||
assert_eq!(run1(" -345 "), (-345, 5));
|
||||
assert_eq!(run1(" 0345 "), (229, 6));
|
||||
assert_eq!(run1(" +0345 "), (229, 7));
|
||||
assert_eq!(run1(" -0345 "), (-229, 7));
|
||||
assert_eq!(run1(" 0x345 "), (0x345, 6));
|
||||
assert_eq!(run1(" -0x345 "), (-0x345, 7));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue