diff --git a/fish-rust/Cargo.lock b/fish-rust/Cargo.lock index 0b49baf3f..059017816 100644 --- a/fish-rust/Cargo.lock +++ b/fish-rust/Cargo.lock @@ -81,7 +81,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "autocxx" version = "0.23.1" -source = "git+https://github.com/fish-shell/autocxx?branch=fish#311485f38289a352dcaddaad7f819f93f6e7df99" +source = "git+https://github.com/fish-shell/autocxx?branch=fish#f9ed164fed6a35a572d19f1495b0691e4b3fd92b" dependencies = [ "aquamarine", "autocxx-macro", @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "autocxx-build" version = "0.23.1" -source = "git+https://github.com/fish-shell/autocxx?branch=fish#311485f38289a352dcaddaad7f819f93f6e7df99" +source = "git+https://github.com/fish-shell/autocxx?branch=fish#f9ed164fed6a35a572d19f1495b0691e4b3fd92b" dependencies = [ "autocxx-engine", "env_logger", @@ -125,7 +125,7 @@ dependencies = [ [[package]] name = "autocxx-engine" version = "0.23.1" -source = "git+https://github.com/fish-shell/autocxx?branch=fish#311485f38289a352dcaddaad7f819f93f6e7df99" +source = "git+https://github.com/fish-shell/autocxx?branch=fish#f9ed164fed6a35a572d19f1495b0691e4b3fd92b" dependencies = [ "aquamarine", "autocxx-bindgen", @@ -154,7 +154,7 @@ dependencies = [ [[package]] name = "autocxx-macro" version = "0.23.1" -source = "git+https://github.com/fish-shell/autocxx?branch=fish#311485f38289a352dcaddaad7f819f93f6e7df99" +source = "git+https://github.com/fish-shell/autocxx?branch=fish#f9ed164fed6a35a572d19f1495b0691e4b3fd92b" dependencies = [ "autocxx-parser", "proc-macro-error", @@ -166,7 +166,7 @@ dependencies = [ [[package]] name = "autocxx-parser" version = "0.23.1" -source = "git+https://github.com/fish-shell/autocxx?branch=fish#311485f38289a352dcaddaad7f819f93f6e7df99" +source = "git+https://github.com/fish-shell/autocxx?branch=fish#f9ed164fed6a35a572d19f1495b0691e4b3fd92b" dependencies = [ "indexmap", "itertools 0.10.5", @@ -258,7 +258,7 @@ dependencies = [ [[package]] name = "cxx" version = "1.0.81" -source = "git+https://github.com/fish-shell/cxx?branch=fish#24d1bac1da6abbc2b483760358676e95262aca63" +source = "git+https://github.com/fish-shell/cxx?branch=fish#2b1b38264b7d10ffd946ece72b6a0f00830b3769" dependencies = [ "cc", "cxxbridge-flags", @@ -270,7 +270,7 @@ dependencies = [ [[package]] name = "cxx-build" version = "1.0.81" -source = "git+https://github.com/fish-shell/cxx?branch=fish#24d1bac1da6abbc2b483760358676e95262aca63" +source = "git+https://github.com/fish-shell/cxx?branch=fish#2b1b38264b7d10ffd946ece72b6a0f00830b3769" dependencies = [ "cc", "codespan-reporting", @@ -284,7 +284,7 @@ dependencies = [ [[package]] name = "cxx-gen" version = "0.7.81" -source = "git+https://github.com/fish-shell/cxx?branch=fish#24d1bac1da6abbc2b483760358676e95262aca63" +source = "git+https://github.com/fish-shell/cxx?branch=fish#2b1b38264b7d10ffd946ece72b6a0f00830b3769" dependencies = [ "codespan-reporting", "proc-macro2", @@ -295,12 +295,12 @@ dependencies = [ [[package]] name = "cxxbridge-flags" version = "1.0.81" -source = "git+https://github.com/fish-shell/cxx?branch=fish#24d1bac1da6abbc2b483760358676e95262aca63" +source = "git+https://github.com/fish-shell/cxx?branch=fish#2b1b38264b7d10ffd946ece72b6a0f00830b3769" [[package]] name = "cxxbridge-macro" version = "1.0.81" -source = "git+https://github.com/fish-shell/cxx?branch=fish#24d1bac1da6abbc2b483760358676e95262aca63" +source = "git+https://github.com/fish-shell/cxx?branch=fish#2b1b38264b7d10ffd946ece72b6a0f00830b3769" dependencies = [ "proc-macro2", "quote", @@ -381,6 +381,7 @@ dependencies = [ "num-traits", "once_cell", "pcre2", + "printf-compat", "rand", "unixstring", "widestring", @@ -787,6 +788,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "printf-compat" +version = "0.1.1" +source = "git+https://github.com/fish-shell/printf-compat.git?branch=fish#d5f98dc8ce7a63e6639b08082ffbc6499021260c" +dependencies = [ + "bitflags", + "itertools 0.9.0", + "libc", + "widestring", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -813,9 +825,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" dependencies = [ "unicode-ident", ] diff --git a/fish-rust/Cargo.toml b/fish-rust/Cargo.toml index 98d911ba6..24f803e47 100644 --- a/fish-rust/Cargo.toml +++ b/fish-rust/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.67" widestring-suffix = { path = "./widestring-suffix/" } pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", branch = "master", default-features = false, features = ["utf32"] } fast-float = { git = "https://github.com/fish-shell/fast-float-rust", branch="fish" } +printf-compat = { git = "https://github.com/fish-shell/printf-compat.git", branch="fish" } autocxx = "0.23.1" bitflags = "1.3.2" diff --git a/fish-rust/src/builtins/abbr.rs b/fish-rust/src/builtins/abbr.rs index 9cced45cb..68935eb6e 100644 --- a/fish-rust/src/builtins/abbr.rs +++ b/fish-rust/src/builtins/abbr.rs @@ -191,7 +191,7 @@ fn abbr_list(opts: &Options, streams: &mut io_streams_t) -> Option { "%ls %ls: Unexpected argument -- '%ls'\n", CMD, subcmd, - opts.args[0] + &opts.args[0] )); return STATUS_INVALID_ARGS; } diff --git a/fish-rust/src/builtins/emit.rs b/fish-rust/src/builtins/emit.rs index 16009de1e..5ddc3f258 100644 --- a/fish-rust/src/builtins/emit.rs +++ b/fish-rust/src/builtins/emit.rs @@ -7,7 +7,7 @@ use crate::event; use crate::ffi::parser_t; use crate::wchar::{wstr, WString}; -use crate::wutil::format::printf::sprintf; +use crate::wutil::printf::sprintf; #[widestrs] pub fn emit( diff --git a/fish-rust/src/builtins/random.rs b/fish-rust/src/builtins/random.rs index 232088d3f..747a81821 100644 --- a/fish-rust/src/builtins/random.rs +++ b/fish-rust/src/builtins/random.rs @@ -7,9 +7,7 @@ use crate::ffi::parser_t; use crate::wchar::{wstr, L}; use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t}; -use crate::wutil::{ - self, fish_wcstoi_opts, format::printf::sprintf, wgettext_fmt, Options as WcstoiOptions, -}; +use crate::wutil::{self, fish_wcstoi_opts, sprintf, wgettext_fmt, Options as WcstoiOptions}; use num_traits::PrimInt; use once_cell::sync::Lazy; use rand::rngs::SmallRng; @@ -176,6 +174,6 @@ fn parse( // Safe because end was a valid i64 and the result here is in the range start..=end. let result: i64 = start.checked_add_unsigned(rand * step).unwrap(); - streams.out.append(sprintf!(L!("%d\n"), result)); + streams.out.append(sprintf!(L!("%lld\n"), result)); return STATUS_CMD_OK; } diff --git a/fish-rust/src/termsize.rs b/fish-rust/src/termsize.rs index bd87dc8c6..7442bc227 100644 --- a/fish-rust/src/termsize.rs +++ b/fish-rust/src/termsize.rs @@ -42,7 +42,7 @@ pub struct Termsize { fn var_to_int_or(var: Option, default: isize) -> isize { match var { Some(s) => { - let proposed = fish_wcstoi(s.chars()); + let proposed = fish_wcstoi(&s); if let Ok(proposed) = proposed { proposed } else { diff --git a/fish-rust/src/wutil/format/format.rs b/fish-rust/src/wutil/format/format.rs deleted file mode 100644 index b71e3203b..000000000 --- a/fish-rust/src/wutil/format/format.rs +++ /dev/null @@ -1,532 +0,0 @@ -// Adapted from https://github.com/tjol/sprintf-rs -// License follows: -// -// Copyright (c) 2021 Thomas Jollans -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF -// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -use std::convert::{TryFrom, TryInto}; - -use super::parser::{ConversionSpecifier, ConversionType, NumericParam}; -use super::printf::{PrintfError, Result}; -use crate::wchar::{wstr, WExt, WString, L}; - -/// Trait for types that can be formatted using printf strings -/// -/// Implemented for the basic types and shouldn't need implementing for -/// anything else. -pub trait Printf { - /// Format `self` based on the conversion configured in `spec`. - fn format(&self, spec: &ConversionSpecifier) -> Result; - /// Get `self` as an integer for use as a field width, if possible. - /// Defaults to None. - fn as_int(&self) -> Option { - None - } -} - -impl Printf for u64 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - let mut base = 10; - let mut digits: Vec = "0123456789".chars().collect(); - let mut alt_prefix = L!(""); - match spec.conversion_type { - ConversionType::DecInt => {} - ConversionType::HexIntLower => { - base = 16; - digits = "0123456789abcdef".chars().collect(); - alt_prefix = L!("0x"); - } - ConversionType::HexIntUpper => { - base = 16; - digits = "0123456789ABCDEF".chars().collect(); - alt_prefix = L!("0X"); - } - ConversionType::OctInt => { - base = 8; - digits = "01234567".chars().collect(); - alt_prefix = L!("0"); - } - _ => { - return Err(PrintfError::WrongType); - } - } - let prefix = if spec.alt_form { - alt_prefix.to_owned() - } else { - WString::new() - }; - - // Build the actual number (in reverse) - let mut rev_num = WString::new(); - let mut n = *self; - while n > 0 { - let digit = n % base; - n /= base; - rev_num.push(digits[digit as usize]); - } - if rev_num.is_empty() { - rev_num.push('0'); - } - - // Take care of padding - let width: usize = match spec.width { - NumericParam::Literal(w) => w, - _ => { - return Err(PrintfError::Unknown); // should not happen at this point!! - } - } - .try_into() - .unwrap_or_default(); - let formatted = if spec.left_adj { - let mut num_str = prefix; - num_str.extend(rev_num.chars().rev()); - while num_str.len() < width { - num_str.push(' '); - } - num_str - } else if spec.zero_pad { - while prefix.len() + rev_num.len() < width { - rev_num.push('0'); - } - let mut num_str = prefix; - num_str.extend(rev_num.chars().rev()); - num_str - } else { - let mut num_str = prefix; - num_str.extend(rev_num.chars().rev()); - while num_str.len() < width { - num_str.insert(0, ' '); - } - num_str - }; - - Ok(formatted) - } - fn as_int(&self) -> Option { - i32::try_from(*self).ok() - } -} - -impl Printf for i64 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - match spec.conversion_type { - // signed integer format - ConversionType::DecInt => { - // do I need a sign prefix? - let negative = *self < 0; - let abs_val = self.abs(); - let sign_prefix: &wstr = if negative { - L!("-") - } else if spec.force_sign { - L!("+") - } else if spec.space_sign { - L!(" ") - } else { - L!("") - }; - let mut mod_spec = *spec; - mod_spec.width = match spec.width { - NumericParam::Literal(w) => NumericParam::Literal(w - sign_prefix.len() as i32), - _ => { - return Err(PrintfError::Unknown); - } - }; - - let formatted = (abs_val as u64).format(&mod_spec)?; - // put the sign a after any leading spaces - let mut actual_number = &formatted[0..]; - let mut leading_spaces = &formatted[0..0]; - if let Some(first_non_space) = formatted.chars().position(|c| c != ' ') { - actual_number = &formatted[first_non_space..]; - leading_spaces = &formatted[0..first_non_space]; - } - Ok(leading_spaces.to_owned() + sign_prefix + actual_number) - } - // unsigned-only formats - ConversionType::HexIntLower | ConversionType::HexIntUpper | ConversionType::OctInt => { - (*self as u64).format(spec) - } - _ => Err(PrintfError::WrongType), - } - } - fn as_int(&self) -> Option { - i32::try_from(*self).ok() - } -} - -impl Printf for i32 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - match spec.conversion_type { - // signed integer format - ConversionType::DecInt => (*self as i64).format(spec), - // unsigned-only formats - ConversionType::HexIntLower | ConversionType::HexIntUpper | ConversionType::OctInt => { - (*self as u32).format(spec) - } - _ => Err(PrintfError::WrongType), - } - } - fn as_int(&self) -> Option { - Some(*self) - } -} - -impl Printf for u32 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - (*self as u64).format(spec) - } - fn as_int(&self) -> Option { - i32::try_from(*self).ok() - } -} - -impl Printf for i16 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - match spec.conversion_type { - // signed integer format - ConversionType::DecInt => (*self as i64).format(spec), - // unsigned-only formats - ConversionType::HexIntLower | ConversionType::HexIntUpper | ConversionType::OctInt => { - (*self as u16).format(spec) - } - _ => Err(PrintfError::WrongType), - } - } - fn as_int(&self) -> Option { - Some(*self as i32) - } -} - -impl Printf for u16 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - (*self as u64).format(spec) - } - fn as_int(&self) -> Option { - Some(*self as i32) - } -} - -impl Printf for i8 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - match spec.conversion_type { - // signed integer format - ConversionType::DecInt => (*self as i64).format(spec), - // unsigned-only formats - ConversionType::HexIntLower | ConversionType::HexIntUpper | ConversionType::OctInt => { - (*self as u8).format(spec) - } - _ => Err(PrintfError::WrongType), - } - } - fn as_int(&self) -> Option { - Some(*self as i32) - } -} - -impl Printf for u8 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - (*self as u64).format(spec) - } - fn as_int(&self) -> Option { - Some(*self as i32) - } -} - -impl Printf for usize { - fn format(&self, spec: &ConversionSpecifier) -> Result { - (*self as u64).format(spec) - } - fn as_int(&self) -> Option { - i32::try_from(*self).ok() - } -} - -impl Printf for isize { - fn format(&self, spec: &ConversionSpecifier) -> Result { - (*self as u64).format(spec) - } - fn as_int(&self) -> Option { - i32::try_from(*self).ok() - } -} - -impl Printf for f64 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - let mut prefix = WString::new(); - let mut number = WString::new(); - - // set up the sign - if self.is_sign_negative() { - prefix.push('-'); - } else if spec.space_sign { - prefix.push(' '); - } else if spec.force_sign { - prefix.push('+'); - } - - if self.is_finite() { - let mut use_scientific = false; - let mut exp_symb = 'e'; - let mut strip_trailing_0s = false; - let mut abs = self.abs(); - let mut exponent = abs.log10().floor() as i32; - let mut precision = match spec.precision { - NumericParam::Literal(p) => p, - _ => { - return Err(PrintfError::Unknown); - } - }; - if precision <= 0 { - precision = 0; - } - match spec.conversion_type { - ConversionType::DecFloatLower | ConversionType::DecFloatUpper => { - // default - } - ConversionType::SciFloatLower => { - use_scientific = true; - } - ConversionType::SciFloatUpper => { - use_scientific = true; - exp_symb = 'E'; - } - ConversionType::CompactFloatLower | ConversionType::CompactFloatUpper => { - if spec.conversion_type == ConversionType::CompactFloatUpper { - exp_symb = 'E' - } - strip_trailing_0s = true; - if precision == 0 { - precision = 1; - } - // exponent signifies significant digits - we must round now - // to (re)calculate the exponent - let rounding_factor = 10.0_f64.powf((precision - 1 - exponent) as f64); - let rounded_fixed = (abs * rounding_factor).round(); - abs = rounded_fixed / rounding_factor; - exponent = abs.log10().floor() as i32; - if exponent < -4 || exponent >= precision { - use_scientific = true; - precision -= 1; - } else { - // precision specifies the number of significant digits - precision -= 1 + exponent; - } - } - _ => { - return Err(PrintfError::WrongType); - } - } - - if use_scientific { - let mut normal = abs / 10.0_f64.powf(exponent as f64); - - if precision > 0 { - let mut int_part = normal.trunc(); - let mut exp_factor = 10.0_f64.powf(precision as f64); - let mut tail = ((normal - int_part) * exp_factor).round() as u64; - while tail >= exp_factor as u64 { - // Overflow, must round - int_part += 1.0; - tail -= exp_factor as u64; - if int_part >= 10.0 { - // keep same precision - which means changing exponent - exponent += 1; - exp_factor /= 10.0; - normal /= 10.0; - int_part = normal.trunc(); - tail = ((normal - int_part) * exp_factor).round() as u64; - } - } - - let mut rev_tail_str = WString::new(); - for _ in 0..precision { - rev_tail_str.push((b'0' + (tail % 10) as u8) as char); - tail /= 10; - } - number.push_str(&int_part.to_string()); - number.push('.'); - number.extend(rev_tail_str.chars().rev()); - if strip_trailing_0s { - while number.ends_with('0') { - number.pop(); - } - } - } else { - number.push_str(&format!("{}", normal.round())); - } - number.push(exp_symb); - number.push_str(&format!("{exponent:+03}")); - } else if precision > 0 { - let mut int_part = abs.trunc(); - let exp_factor = 10.0_f64.powf(precision as f64); - let mut tail = ((abs - int_part) * exp_factor).round() as u64; - let mut rev_tail_str = WString::new(); - if tail >= exp_factor as u64 { - // overflow - we must round up - int_part += 1.0; - tail -= exp_factor as u64; - // no need to change the exponent as we don't have one - // (not scientific notation) - } - for _ in 0..precision { - rev_tail_str.push((b'0' + (tail % 10) as u8) as char); - tail /= 10; - } - number.push_str(&int_part.to_string()); - number.push('.'); - number.extend(rev_tail_str.chars().rev()); - if strip_trailing_0s { - while number.ends_with('0') { - number.pop(); - } - } - } else { - number.push_str(&format!("{}", abs.round())); - } - } else { - // not finite - match spec.conversion_type { - ConversionType::DecFloatLower - | ConversionType::SciFloatLower - | ConversionType::CompactFloatLower => { - if self.is_infinite() { - number.push_str("inf") - } else { - number.push_str("nan") - } - } - ConversionType::DecFloatUpper - | ConversionType::SciFloatUpper - | ConversionType::CompactFloatUpper => { - if self.is_infinite() { - number.push_str("INF") - } else { - number.push_str("NAN") - } - } - _ => { - return Err(PrintfError::WrongType); - } - } - } - // Take care of padding - let width: usize = match spec.width { - NumericParam::Literal(w) => w, - _ => { - return Err(PrintfError::Unknown); // should not happen at this point!! - } - } - .try_into() - .unwrap_or_default(); - let formatted = if spec.left_adj { - let mut full_num = prefix + &*number; - while full_num.len() < width { - full_num.push(' '); - } - full_num - } else if spec.zero_pad && self.is_finite() { - while prefix.len() + number.len() < width { - prefix.push('0'); - } - prefix + &*number - } else { - let mut full_num = prefix + &*number; - while full_num.len() < width { - full_num.insert(0, ' '); - } - full_num - }; - Ok(formatted) - } - fn as_int(&self) -> Option { - None - } -} - -impl Printf for f32 { - fn format(&self, spec: &ConversionSpecifier) -> Result { - (*self as f64).format(spec) - } -} - -impl Printf for &wstr { - fn format(&self, spec: &ConversionSpecifier) -> Result { - if spec.conversion_type == ConversionType::String { - Ok((*self).to_owned()) - } else { - Err(PrintfError::WrongType) - } - } -} - -impl Printf for &str { - fn format(&self, spec: &ConversionSpecifier) -> Result { - if spec.conversion_type == ConversionType::String { - add_padding((*self).into(), spec) - } else { - Err(PrintfError::WrongType) - } - } -} - -impl Printf for char { - fn format(&self, spec: &ConversionSpecifier) -> Result { - if spec.conversion_type == ConversionType::Char { - let mut s = WString::new(); - s.push(*self); - Ok(s) - } else { - Err(PrintfError::WrongType) - } - } -} - -impl Printf for String { - fn format(&self, spec: &ConversionSpecifier) -> Result { - self.as_str().format(spec) - } -} - -impl Printf for WString { - fn format(&self, spec: &ConversionSpecifier) -> Result { - self.as_utfstr().format(spec) - } -} - -impl Printf for &WString { - fn format(&self, spec: &ConversionSpecifier) -> Result { - self.as_utfstr().format(spec) - } -} - -fn add_padding(mut s: WString, spec: &ConversionSpecifier) -> Result { - let width: usize = match spec.width { - NumericParam::Literal(w) => w, - _ => { - return Err(PrintfError::Unknown); // should not happen at this point!! - } - } - .try_into() - .unwrap_or_default(); - if s.len() < width { - let padding = L!(" ").repeat(width - s.len()); - s.insert_utfstr(0, &padding); - }; - Ok(s) -} diff --git a/fish-rust/src/wutil/format/mod.rs b/fish-rust/src/wutil/format/mod.rs deleted file mode 100644 index 67fbedb38..000000000 --- a/fish-rust/src/wutil/format/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[allow(clippy::module_inception)] -mod format; -mod parser; -pub mod printf; - -#[cfg(test)] -mod tests; diff --git a/fish-rust/src/wutil/format/parser.rs b/fish-rust/src/wutil/format/parser.rs deleted file mode 100644 index 074e80601..000000000 --- a/fish-rust/src/wutil/format/parser.rs +++ /dev/null @@ -1,218 +0,0 @@ -// Adapted from https://github.com/tjol/sprintf-rs -// License follows: -// -// Copyright (c) 2021 Thomas Jollans -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF -// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -use super::printf::{PrintfError, Result}; -use crate::wchar::{wstr, WExt, WString}; - -#[derive(Debug, Clone)] -pub enum FormatElement { - Verbatim(WString), - Format(ConversionSpecifier), -} - -/// Parsed printf conversion specifier -#[derive(Debug, Clone, Copy)] -pub struct ConversionSpecifier { - /// flag `#`: use `0x`, etc? - pub alt_form: bool, - /// flag `0`: left-pad with zeros? - pub zero_pad: bool, - /// flag `-`: left-adjust (pad with spaces on the right) - pub left_adj: bool, - /// flag `' '` (space): indicate sign with a space? - pub space_sign: bool, - /// flag `+`: Always show sign? (for signed numbers) - pub force_sign: bool, - /// field width - pub width: NumericParam, - /// floating point field precision - pub precision: NumericParam, - /// data type - pub conversion_type: ConversionType, -} - -/// Width / precision parameter -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum NumericParam { - /// The literal width - Literal(i32), - /// Get the width from the previous argument - /// - /// This should never be passed to [Printf::format()][super::format::Printf::format()]. - FromArgument, -} - -/// Printf data type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ConversionType { - /// `d`, `i`, or `u` - DecInt, - /// `o` - OctInt, - /// `x` or `p` - HexIntLower, - /// `X` - HexIntUpper, - /// `e` - SciFloatLower, - /// `E` - SciFloatUpper, - /// `f` - DecFloatLower, - /// `F` - DecFloatUpper, - /// `g` - CompactFloatLower, - /// `G` - CompactFloatUpper, - /// `c` - Char, - /// `s` - String, - /// `%` - PercentSign, -} - -pub(crate) fn parse_format_string(fmt: &wstr) -> Result> { - // find the first % - let mut res = Vec::new(); - let parts: Vec<&wstr> = match fmt.find_char('%') { - Some(i) => vec![&fmt[..i], &fmt[(i + 1)..]], - None => vec![fmt], - }; - if !parts[0].is_empty() { - res.push(FormatElement::Verbatim(parts[0].to_owned())); - } - if parts.len() > 1 { - let (spec, rest) = take_conversion_specifier(parts[1])?; - res.push(FormatElement::Format(spec)); - res.append(&mut parse_format_string(rest)?); - } - - Ok(res) -} - -fn take_conversion_specifier(s: &wstr) -> Result<(ConversionSpecifier, &wstr)> { - let mut spec = ConversionSpecifier { - alt_form: false, - zero_pad: false, - left_adj: false, - space_sign: false, - force_sign: false, - width: NumericParam::Literal(0), - precision: NumericParam::Literal(6), - // ignore length modifier - conversion_type: ConversionType::DecInt, - }; - - let mut s = s; - - // parse flags - loop { - match s.chars().next() { - Some('#') => { - spec.alt_form = true; - } - Some('0') => { - spec.zero_pad = true; - } - Some('-') => { - spec.left_adj = true; - } - Some(' ') => { - spec.space_sign = true; - } - Some('+') => { - spec.force_sign = true; - } - _ => { - break; - } - } - s = &s[1..]; - } - // parse width - let (w, mut s) = take_numeric_param(s); - spec.width = w; - // parse precision - if matches!(s.chars().next(), Some('.')) { - s = &s[1..]; - let (p, s2) = take_numeric_param(s); - spec.precision = p; - s = s2; - } - // check length specifier - for len_spec in ["hh", "h", "l", "ll", "q", "L", "j", "z", "Z", "t"] { - if s.starts_with(len_spec) { - s = &s[len_spec.len()..]; - break; // only allow one length specifier - } - } - // parse conversion type - spec.conversion_type = match s.chars().next() { - Some('i') | Some('d') | Some('u') => ConversionType::DecInt, - Some('o') => ConversionType::OctInt, - Some('x') => ConversionType::HexIntLower, - Some('X') => ConversionType::HexIntUpper, - Some('e') => ConversionType::SciFloatLower, - Some('E') => ConversionType::SciFloatUpper, - Some('f') => ConversionType::DecFloatLower, - Some('F') => ConversionType::DecFloatUpper, - Some('g') => ConversionType::CompactFloatLower, - Some('G') => ConversionType::CompactFloatUpper, - Some('c') | Some('C') => ConversionType::Char, - Some('s') | Some('S') => ConversionType::String, - Some('p') => { - spec.alt_form = true; - ConversionType::HexIntLower - } - Some('%') => ConversionType::PercentSign, - _ => { - return Err(PrintfError::ParseError); - } - }; - - Ok((spec, &s[1..])) -} - -fn take_numeric_param(s: &wstr) -> (NumericParam, &wstr) { - match s.chars().next() { - Some('*') => (NumericParam::FromArgument, &s[1..]), - Some(digit) if ('1'..='9').contains(&digit) => { - let mut s = s; - let mut w = 0; - loop { - match s.chars().next() { - Some(digit) if ('0'..='9').contains(&digit) => { - w = 10 * w + (digit as i32 - '0' as i32); - } - _ => { - break; - } - } - s = &s[1..]; - } - (NumericParam::Literal(w), s) - } - _ => (NumericParam::Literal(0), s), - } -} diff --git a/fish-rust/src/wutil/format/printf.rs b/fish-rust/src/wutil/format/printf.rs deleted file mode 100644 index 0324ee96f..000000000 --- a/fish-rust/src/wutil/format/printf.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Adapted from https://github.com/tjol/sprintf-rs -// License follows: -// -// Copyright (c) 2021 Thomas Jollans -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF -// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pub use super::format::Printf; -use super::parser::{parse_format_string, ConversionType, FormatElement, NumericParam}; -use crate::wchar::{wstr, WString}; - -/// Error type -#[derive(Debug, Clone, Copy)] -pub enum PrintfError { - /// Error parsing the format string - ParseError, - /// Incorrect type passed as an argument - WrongType, - /// Too many arguments passed - TooManyArgs, - /// Too few arguments passed - NotEnoughArgs, - /// Other error (should never happen) - Unknown, -} - -pub type Result = std::result::Result; - -/// Format a string. (Roughly equivalent to `vsnprintf` or `vasprintf` in C) -/// -/// Takes a printf-style format string `format` and a slice of dynamically -/// typed arguments, `args`. -/// -/// use sprintf::{vsprintf, Printf}; -/// let n = 16; -/// let args: Vec<&dyn Printf> = vec![&n]; -/// let s = vsprintf("%#06x", &args).unwrap(); -/// assert_eq!(s, "0x0010"); -/// -/// See also: [sprintf] -pub fn vsprintf(format: &wstr, args: &[&dyn Printf]) -> Result { - vsprintfp(&parse_format_string(format)?, args) -} - -fn vsprintfp(format: &[FormatElement], args: &[&dyn Printf]) -> Result { - let mut res = WString::new(); - - let mut args = args; - let mut pop_arg = || { - if args.is_empty() { - Err(PrintfError::NotEnoughArgs) - } else { - let a = args[0]; - args = &args[1..]; - Ok(a) - } - }; - - for elem in format { - match elem { - FormatElement::Verbatim(s) => { - res.push_utfstr(s); - } - FormatElement::Format(spec) => { - if spec.conversion_type == ConversionType::PercentSign { - res.push('%'); - } else { - let mut completed_spec = *spec; - if spec.width == NumericParam::FromArgument { - completed_spec.width = NumericParam::Literal( - pop_arg()?.as_int().ok_or(PrintfError::WrongType)?, - ) - } - if spec.precision == NumericParam::FromArgument { - completed_spec.precision = NumericParam::Literal( - pop_arg()?.as_int().ok_or(PrintfError::WrongType)?, - ) - } - res.push_utfstr(&pop_arg()?.format(&completed_spec)?); - } - } - } - } - - if args.is_empty() { - Ok(res) - } else { - Err(PrintfError::TooManyArgs) - } -} - -/// Format a string. (Roughly equivalent to `snprintf` or `asprintf` in C) -/// -/// Takes a printf-style format string `format` and a variable number of -/// additional arguments. -/// -/// use sprintf::sprintf; -/// let s = sprintf!("%s = %*d", "forty-two", 4, 42); -/// assert_eq!(s, "forty-two = 42"); -/// -/// Wrapper around [vsprintf]. -macro_rules! sprintf { - // Variant which allows a string literal. - ( - $fmt:literal, // format string - $($arg:expr),* // arguments - $(,)? // optional trailing comma - ) => { - crate::wutil::format::printf::vsprintf(&crate::wchar::L!($fmt), &[$( &($arg) as &dyn crate::wutil::format::printf::Printf),* ][..]).expect("Invalid format string and/or arguments") - }; - - // Variant which allows a runtime format string, which must be of type &wstr. - ( - $fmt:expr, // format string - $($arg:expr),* // arguments - $(,)? // optional trailing comma - ) => { - crate::wutil::format::printf::vsprintf($fmt, &[$( &($arg) as &dyn crate::wutil::format::printf::Printf),* ][..]).expect("Invalid format string and/or arguments") - }; -} - -pub(crate) use sprintf; - -#[cfg(test)] -mod tests { - use super::*; - use crate::wchar::L; - - // Test basic printf with both literals and wide strings. - #[test] - fn test_sprintf() { - assert_eq!(sprintf!("Hello, %s!", "world"), "Hello, world!"); - assert_eq!(sprintf!(L!("Hello, %ls!"), "world"), "Hello, world!"); - assert_eq!(sprintf!(L!("Hello, %ls!"), L!("world")), "Hello, world!"); - } -} diff --git a/fish-rust/src/wutil/format/tests.rs b/fish-rust/src/wutil/format/tests.rs deleted file mode 100644 index 94fce7c29..000000000 --- a/fish-rust/src/wutil/format/tests.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Adapted from https://github.com/tjol/sprintf-rs -// License follows: -// -// Copyright (c) 2021 Thomas Jollans -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is furnished -// to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF -// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -use super::printf::{sprintf, Printf}; -use crate::wchar::{widestrs, WString, L}; - -fn check_fmt(nfmt: &str, arg: T, expected: &str) { - let fmt: WString = nfmt.into(); - let our_result = sprintf!(&fmt, arg); - assert_eq!(our_result, expected); -} - -fn check_fmt_2(nfmt: &str, arg: T, arg2: T2, expected: &str) { - let fmt: WString = nfmt.into(); - let our_result = sprintf!(&fmt, arg, arg2); - assert_eq!(our_result, expected); -} - -#[test] -fn test_int() { - check_fmt("%d", 12, "12"); - check_fmt("~%d~", 148, "~148~"); - check_fmt("00%dxx", -91232, "00-91232xx"); - check_fmt("%x", -9232, "ffffdbf0"); - check_fmt("%X", 432, "1B0"); - check_fmt("%09X", 432, "0000001B0"); - check_fmt("%9X", 432, " 1B0"); - check_fmt("%+9X", 492, " 1EC"); - check_fmt("% #9x", 4589, " 0x11ed"); - check_fmt("%2o", 4, " 4"); - check_fmt("% 12d", -4, " -4"); - check_fmt("% 12d", 48, " 48"); - check_fmt("%ld", -4_i64, "-4"); - check_fmt("%lX", -4_i64, "FFFFFFFFFFFFFFFC"); - check_fmt("%ld", 48_i64, "48"); - check_fmt("%-8hd", -12_i16, "-12 "); -} - -#[test] -fn test_float() { - check_fmt("%f", -46.38, "-46.380000"); - check_fmt("%012.3f", 1.2, "00000001.200"); - check_fmt("%012.3e", 1.7, "0001.700e+00"); - check_fmt("%e", 1e300, "1.000000e+300"); - check_fmt("%012.3g%%!", 2.6, "0000000002.6%!"); - check_fmt("%012.5G", -2.69, "-00000002.69"); - check_fmt("%+7.4f", 42.785, "+42.7850"); - check_fmt("{}% 7.4E", 493.12, "{} 4.9312E+02"); - check_fmt("% 7.4E", -120.3, "-1.2030E+02"); - check_fmt("%-10F", f64::INFINITY, "INF "); - check_fmt("%+010F", f64::INFINITY, " +INF"); - check_fmt("% f", f64::NAN, " nan"); - check_fmt("%+f", f64::NAN, "+nan"); - check_fmt("%.1f", 999.99, "1000.0"); - check_fmt("%.1f", 9.99, "10.0"); - check_fmt("%.1e", 9.99, "1.0e+01"); - check_fmt("%.2f", 9.99, "9.99"); - check_fmt("%.2e", 9.99, "9.99e+00"); - check_fmt("%.3f", 9.99, "9.990"); - check_fmt("%.3e", 9.99, "9.990e+00"); - check_fmt("%.1g", 9.99, "1e+01"); - check_fmt("%.1G", 9.99, "1E+01"); - check_fmt("%.1f", 2.99, "3.0"); - check_fmt("%.1e", 2.99, "3.0e+00"); - check_fmt("%.1g", 2.99, "3"); - check_fmt("%.1f", 2.599, "2.6"); - check_fmt("%.1e", 2.599, "2.6e+00"); - check_fmt("%.1g", 2.599, "3"); -} - -#[test] -fn test_str() { - check_fmt( - "test %% with string: %s yay\n", - "FOO", - "test % with string: FOO yay\n", - ); - check_fmt("test char %c", '~', "test char ~"); - check_fmt_2("%*ls", 5, "^", " ^"); -} - -#[test] -#[widestrs] -fn test_str_concat() { - assert_eq!(sprintf!("%s-%ls"L, "abc", "def"L), "abc-def"L); - assert_eq!(sprintf!("%s-%ls"L, "abc", "def"L), "abc-def"L); -} - -#[test] -#[should_panic] -fn test_bad_format() { - sprintf!(L!("%s"), 123); -} - -#[test] -#[should_panic] -fn test_missing_arg() { - sprintf!(L!("%s-%s"), "abc"); -} - -#[test] -#[should_panic] -fn test_too_many_args() { - sprintf!(L!("%d"), 1, 2, 3); -} diff --git a/fish-rust/src/wutil/mod.rs b/fish-rust/src/wutil/mod.rs index 56e11c70f..f3954790a 100644 --- a/fish-rust/src/wutil/mod.rs +++ b/fish-rust/src/wutil/mod.rs @@ -1,21 +1,20 @@ pub mod errors; -pub mod format; pub mod gettext; mod normalize_path; +pub mod printf; pub mod wcstod; pub mod wcstoi; mod wrealpath; -use std::io::Write; - use crate::wchar::{wstr, WString}; -pub(crate) use format::printf::sprintf; pub(crate) use gettext::{wgettext, wgettext_fmt}; pub use normalize_path::*; +pub(crate) use printf::sprintf; pub use wcstoi::*; pub use wrealpath::*; /// Port of the wide-string wperror from `src/wutil.cpp` but for rust `&str`. +use std::io::Write; pub fn perror(s: &str) { let e = errno::errno().0; let mut stderr = std::io::stderr().lock(); diff --git a/fish-rust/src/wutil/printf.rs b/fish-rust/src/wutil/printf.rs new file mode 100644 index 000000000..ebef0d072 --- /dev/null +++ b/fish-rust/src/wutil/printf.rs @@ -0,0 +1,16 @@ +// Re-export sprintf macro. +pub(crate) use printf_compat::sprintf; + +#[cfg(test)] +mod tests { + use super::*; + use crate::wchar::L; + + // Test basic sprintf with both literals and wide strings. + #[test] + fn test_sprintf() { + assert_eq!(sprintf!("Hello, %s!", "world"), "Hello, world!"); + assert_eq!(sprintf!(L!("Hello, %ls!"), "world"), "Hello, world!"); + assert_eq!(sprintf!(L!("Hello, %ls!"), L!("world")), "Hello, world!"); + } +}