mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-28 13:53:10 +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 mut retval = STATUS_CMD_OK;
|
||||||
let pids: Vec<i64> = args[opts.optind..]
|
let pids: Vec<i64> = args[opts.optind..]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| {
|
.map(|&arg| {
|
||||||
fish_wcstoi(arg.chars()).unwrap_or_else(|_| {
|
fish_wcstoi(arg).unwrap_or_else(|_| {
|
||||||
streams.err.append(wgettext_fmt!(
|
streams.err.append(wgettext_fmt!(
|
||||||
"%ls: '%ls' is not a valid job specifier\n",
|
"%ls: '%ls' is not a valid job specifier\n",
|
||||||
cmd,
|
cmd,
|
||||||
*arg
|
arg
|
||||||
));
|
));
|
||||||
retval = STATUS_INVALID_ARGS;
|
retval = STATUS_INVALID_ARGS;
|
||||||
0
|
0
|
||||||
|
|
|
@ -73,7 +73,7 @@ pub fn random(
|
||||||
cmd: &wstr,
|
cmd: &wstr,
|
||||||
num: &wstr,
|
num: &wstr,
|
||||||
) -> Result<T, wutil::Error> {
|
) -> 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() {
|
if res.is_err() {
|
||||||
streams
|
streams
|
||||||
.err
|
.err
|
||||||
|
|
|
@ -117,7 +117,7 @@ pub fn parse_return_value(
|
||||||
if optind == args.len() {
|
if optind == args.len() {
|
||||||
Ok(parser.get_last_status().into())
|
Ok(parser.get_last_status().into())
|
||||||
} else {
|
} else {
|
||||||
match fish_wcstoi(args[optind].chars()) {
|
match fish_wcstoi(args[optind]) {
|
||||||
Ok(i) => Ok(i),
|
Ok(i) => Ok(i),
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
streams
|
streams
|
||||||
|
|
|
@ -196,7 +196,7 @@ pub fn wait(
|
||||||
for i in w.woptind..argc {
|
for i in w.woptind..argc {
|
||||||
if iswnumeric(argv[i]) {
|
if iswnumeric(argv[i]) {
|
||||||
// argument is pid
|
// 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 {
|
if mpid.is_err() || mpid.unwrap() <= 0 {
|
||||||
streams.err.append(wgettext_fmt!(
|
streams.err.append(wgettext_fmt!(
|
||||||
"%ls: '%ls' is not a valid process id\n",
|
"%ls: '%ls' is not a valid process id\n",
|
||||||
|
|
|
@ -112,7 +112,7 @@ impl RedirectionSpec {
|
||||||
|
|
||||||
/// Attempt to parse target as an fd.
|
/// Attempt to parse target as an fd.
|
||||||
pub fn get_target_as_fd(&self) -> Option<RawFd> {
|
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> {
|
fn get_target_as_fd_ffi(&self) -> SharedPtr<i32> {
|
||||||
match self.get_target_as_fd() {
|
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.
|
/// \return true if \p prefix is a prefix of \p contents.
|
||||||
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
|
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,22 +1,39 @@
|
||||||
pub use super::errors::Error;
|
pub use super::errors::Error;
|
||||||
|
use crate::wchar::IntoCharIter;
|
||||||
use num_traits::{NumCast, PrimInt};
|
use num_traits::{NumCast, PrimInt};
|
||||||
use std::iter::Peekable;
|
use std::iter::{Fuse, Peekable};
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
|
||||||
struct ParseResult {
|
struct ParseResult {
|
||||||
result: u64,
|
result: u64,
|
||||||
negative: bool,
|
negative: bool,
|
||||||
consumed_all: bool,
|
consumed_all: bool,
|
||||||
|
consumed: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to get the current char, or \0.
|
struct CharsIterator<Iter: Iterator<Item = char>> {
|
||||||
fn current<Chars>(chars: &mut Peekable<Chars>) -> char
|
chars: Peekable<Fuse<Iter>>,
|
||||||
where
|
consumed: usize,
|
||||||
Chars: Iterator<Item = char>,
|
}
|
||||||
{
|
|
||||||
match chars.peek() {
|
impl<Iter: Iterator<Item = char>> CharsIterator<Iter> {
|
||||||
Some(c) => *c,
|
/// Get the current char, or \0.
|
||||||
None => '\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.
|
/// - Leading 0 means 8.
|
||||||
/// - Otherwise 10.
|
/// - Otherwise 10.
|
||||||
/// The parse result contains the number as a u64, and whether it was negative.
|
/// 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>
|
fn fish_parse_radix<Iter: Iterator<Item = char>>(
|
||||||
where
|
iter: Iter,
|
||||||
Chars: Iterator<Item = char>,
|
mradix: Option<u32>,
|
||||||
{
|
) -> Result<ParseResult, Error> {
|
||||||
if let Some(r) = mradix {
|
if let Some(r) = mradix {
|
||||||
assert!((2..=36).contains(&r), "fish_parse_radix: invalid radix {r}");
|
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.
|
// Skip leading whitespace.
|
||||||
while current(chars).is_whitespace() {
|
while chars.current().is_whitespace() {
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,9 +68,9 @@ where
|
||||||
|
|
||||||
// Consume leading +/-.
|
// Consume leading +/-.
|
||||||
let mut negative;
|
let mut negative;
|
||||||
match current(chars) {
|
match chars.current() {
|
||||||
'-' | '+' => {
|
'-' | '+' => {
|
||||||
negative = current(chars) == '-';
|
negative = chars.current() == '-';
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
_ => negative = false,
|
_ => negative = false,
|
||||||
|
@ -57,9 +79,9 @@ where
|
||||||
// Determine the radix.
|
// Determine the radix.
|
||||||
let radix = if let Some(radix) = mradix {
|
let radix = if let Some(radix) = mradix {
|
||||||
radix
|
radix
|
||||||
} else if current(chars) == '0' {
|
} else if chars.current() == '0' {
|
||||||
chars.next();
|
chars.next();
|
||||||
match current(chars) {
|
match chars.current() {
|
||||||
'x' | 'X' => {
|
'x' | 'X' => {
|
||||||
chars.next();
|
chars.next();
|
||||||
16
|
16
|
||||||
|
@ -71,6 +93,7 @@ where
|
||||||
result: 0,
|
result: 0,
|
||||||
negative: false,
|
negative: false,
|
||||||
consumed_all: chars.peek().is_none(),
|
consumed_all: chars.peek().is_none(),
|
||||||
|
consumed: chars.consumed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,19 +102,19 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute as u64.
|
// Compute as u64.
|
||||||
let mut consumed1 = false;
|
let start_consumed = chars.consumed;
|
||||||
let mut result: u64 = 0;
|
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
|
result = result
|
||||||
.checked_mul(radix as u64)
|
.checked_mul(radix as u64)
|
||||||
.and_then(|r| r.checked_add(digit as u64))
|
.and_then(|r| r.checked_add(digit as u64))
|
||||||
.ok_or(Error::Overflow)?;
|
.ok_or(Error::Overflow)?;
|
||||||
chars.next();
|
chars.next();
|
||||||
consumed1 = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did we consume at least one char?
|
// Did we consume at least one char after the prefix?
|
||||||
if !consumed1 {
|
let consumed = chars.consumed;
|
||||||
|
if consumed == start_consumed {
|
||||||
return Err(Error::InvalidChar);
|
return Err(Error::InvalidChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +127,7 @@ where
|
||||||
result,
|
result,
|
||||||
negative,
|
negative,
|
||||||
consumed_all,
|
consumed_all,
|
||||||
|
consumed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +136,7 @@ fn fish_wcstoi_impl<Int, Chars>(
|
||||||
src: Chars,
|
src: Chars,
|
||||||
mradix: Option<u32>,
|
mradix: Option<u32>,
|
||||||
consume_all: bool,
|
consume_all: bool,
|
||||||
|
out_consumed: &mut usize,
|
||||||
) -> Result<Int, Error>
|
) -> Result<Int, Error>
|
||||||
where
|
where
|
||||||
Chars: Iterator<Item = char>,
|
Chars: Iterator<Item = char>,
|
||||||
|
@ -125,8 +150,9 @@ where
|
||||||
result,
|
result,
|
||||||
negative,
|
negative,
|
||||||
consumed_all,
|
consumed_all,
|
||||||
..
|
consumed,
|
||||||
} = fish_parse_radix(src, mradix)?;
|
} = fish_parse_radix(src, mradix)?;
|
||||||
|
*out_consumed = consumed;
|
||||||
|
|
||||||
if !signed && negative {
|
if !signed && negative {
|
||||||
Err(Error::InvalidChar)
|
Err(Error::InvalidChar)
|
||||||
|
@ -158,20 +184,20 @@ where
|
||||||
/// - Leading + is supported.
|
/// - Leading + is supported.
|
||||||
pub fn fish_wcstoi<Int, Chars>(src: Chars) -> Result<Int, Error>
|
pub fn fish_wcstoi<Int, Chars>(src: Chars) -> Result<Int, Error>
|
||||||
where
|
where
|
||||||
Chars: Iterator<Item = char>,
|
Chars: IntoCharIter,
|
||||||
Int: PrimInt,
|
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.
|
/// Convert the given wide string to an integer using the given radix.
|
||||||
/// Leading whitespace is skipped.
|
/// Leading whitespace is skipped.
|
||||||
pub fn fish_wcstoi_radix<Int, Chars>(src: Chars, radix: u32) -> Result<Int, Error>
|
pub fn fish_wcstoi_radix<Int, Chars>(src: Chars, radix: u32) -> Result<Int, Error>
|
||||||
where
|
where
|
||||||
Chars: Iterator<Item = char>,
|
Chars: IntoCharIter,
|
||||||
Int: PrimInt,
|
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>(
|
pub fn fish_wcstoi_radix_all<Int, Chars>(
|
||||||
|
@ -180,10 +206,24 @@ pub fn fish_wcstoi_radix_all<Int, Chars>(
|
||||||
consume_all: bool,
|
consume_all: bool,
|
||||||
) -> Result<Int, Error>
|
) -> Result<Int, Error>
|
||||||
where
|
where
|
||||||
Chars: Iterator<Item = char>,
|
Chars: IntoCharIter,
|
||||||
Int: PrimInt,
|
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)]
|
#[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("-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("-+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));
|
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::u32::MIN, std::u32::MAX);
|
||||||
test_min_max(std::u64::MIN, std::u64::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