diff --git a/fish-rust/src/builtins/bg.rs b/fish-rust/src/builtins/bg.rs index 65026f5cb..affdb02f3 100644 --- a/fish-rust/src/builtins/bg.rs +++ b/fish-rust/src/builtins/bg.rs @@ -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 = 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 diff --git a/fish-rust/src/builtins/random.rs b/fish-rust/src/builtins/random.rs index 2f0dca01d..b6e8533c7 100644 --- a/fish-rust/src/builtins/random.rs +++ b/fish-rust/src/builtins/random.rs @@ -73,7 +73,7 @@ fn parse( cmd: &wstr, num: &wstr, ) -> Result { - 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 diff --git a/fish-rust/src/builtins/return.rs b/fish-rust/src/builtins/return.rs index 6d3f6c5c5..e7d6a020b 100644 --- a/fish-rust/src/builtins/return.rs +++ b/fish-rust/src/builtins/return.rs @@ -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 diff --git a/fish-rust/src/builtins/wait.rs b/fish-rust/src/builtins/wait.rs index ec32ad909..f0bdc43a7 100644 --- a/fish-rust/src/builtins/wait.rs +++ b/fish-rust/src/builtins/wait.rs @@ -196,7 +196,7 @@ pub fn wait( for i in w.woptind..argc { if iswnumeric(argv[i]) { // argument is pid - let mpid: Result = fish_wcstoi(argv[i].chars()); + let mpid: Result = 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", diff --git a/fish-rust/src/redirection.rs b/fish-rust/src/redirection.rs index 973775eef..147d5f4f0 100644 --- a/fish-rust/src/redirection.rs +++ b/fish-rust/src/redirection.rs @@ -112,7 +112,7 @@ pub fn is_close(&self) -> bool { /// Attempt to parse target as an fd. pub fn get_target_as_fd(&self) -> Option { - fish_wcstoi(self.target.as_char_slice().iter().copied()).ok() + fish_wcstoi(&self.target).ok() } fn get_target_as_fd_ffi(&self) -> SharedPtr { match self.get_target_as_fd() { diff --git a/fish-rust/src/wchar_ext.rs b/fish-rust/src/wchar_ext.rs index 47253d009..a9e4ae876 100644 --- a/fish-rust/src/wchar_ext.rs +++ b/fish-rust/src/wchar_ext.rs @@ -116,6 +116,22 @@ fn chars(self) -> Self::Iter { } } +// 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: Prefix, mut contents: Contents) -> bool where diff --git a/fish-rust/src/wutil/wcstoi.rs b/fish-rust/src/wutil/wcstoi.rs index 1164b1b2f..b7c3c8de9 100644 --- a/fish-rust/src/wutil/wcstoi.rs +++ b/fish-rust/src/wutil/wcstoi.rs @@ -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: &mut Peekable) -> char -where - Chars: Iterator, -{ - match chars.peek() { - Some(c) => *c, - None => '\0', +struct CharsIterator> { + chars: Peekable>, + consumed: usize, +} + +impl> CharsIterator { + /// 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 { + self.chars.peek().copied() + } + + /// Get the next char, incrementing self.consumed. + fn next(&mut self) -> Option { + let res = self.chars.next(); + if res.is_some() { + self.consumed += 1; + } + res } } @@ -26,17 +43,22 @@ fn current(chars: &mut Peekable) -> char /// - Leading 0 means 8. /// - Otherwise 10. /// The parse result contains the number as a u64, and whether it was negative. -fn fish_parse_radix(ichars: Chars, mradix: Option) -> Result -where - Chars: Iterator, -{ +fn fish_parse_radix>( + iter: Iter, + mradix: Option, +) -> Result { 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 @@ fn fish_parse_radix(ichars: Chars, mradix: Option) -> Result { - negative = current(chars) == '-'; + negative = chars.current() == '-'; chars.next(); } _ => negative = false, @@ -57,9 +79,9 @@ fn fish_parse_radix(ichars: Chars, mradix: Option) -> Result { chars.next(); 16 @@ -71,6 +93,7 @@ fn fish_parse_radix(ichars: Chars, mradix: Option) -> Result(ichars: Chars, mradix: Option) -> Result(ichars: Chars, mradix: Option) -> Result( src: Chars, mradix: Option, consume_all: bool, + out_consumed: &mut usize, ) -> Result where Chars: Iterator, @@ -125,8 +150,9 @@ fn fish_wcstoi_impl( result, negative, consumed_all, - .. + consumed, } = fish_parse_radix(src, mradix)?; + *out_consumed = consumed; if !signed && negative { Err(Error::InvalidChar) @@ -158,20 +184,20 @@ fn fish_wcstoi_impl( /// - Leading + is supported. pub fn fish_wcstoi(src: Chars) -> Result where - Chars: Iterator, + 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(src: Chars, radix: u32) -> Result where - Chars: Iterator, + 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( @@ -180,10 +206,24 @@ pub fn fish_wcstoi_radix_all( consume_all: bool, ) -> Result where - Chars: Iterator, + 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(src: Chars, out_consumed: &mut usize) -> Result +where + Chars: IntoCharIter, + Int: PrimInt, +{ + fish_wcstoi_impl(src.chars(), None, false, out_consumed) } #[cfg(test)] @@ -205,8 +245,14 @@ fn 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 @@ fn 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)); + } }