mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 21:33:09 +00:00
Replace the printf implementation
The existing printf implementation is too buggy to back the printf builtin. Switch to the new implementation based on printf-compat.
This commit is contained in:
parent
389d25e30f
commit
dad1290337
13 changed files with 49 additions and 1055 deletions
36
fish-rust/Cargo.lock
generated
36
fish-rust/Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -191,7 +191,7 @@ fn abbr_list(opts: &Options, streams: &mut io_streams_t) -> Option<c_int> {
|
|||
"%ls %ls: Unexpected argument -- '%ls'\n",
|
||||
CMD,
|
||||
subcmd,
|
||||
opts.args[0]
|
||||
&opts.args[0]
|
||||
));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::shared::{
|
|||
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(
|
||||
|
|
|
@ -7,9 +7,7 @@ use crate::builtins::shared::{
|
|||
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 @@ pub fn random(
|
|||
// 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;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ static TTY_TERMSIZE_GEN_COUNT: AtomicU32 = AtomicU32::new(0);
|
|||
fn var_to_int_or(var: Option<WString>, 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 {
|
||||
|
|
|
@ -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<WString>;
|
||||
/// Get `self` as an integer for use as a field width, if possible.
|
||||
/// Defaults to None.
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for u64 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
let mut base = 10;
|
||||
let mut digits: Vec<char> = "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> {
|
||||
i32::try_from(*self).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for i64 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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> {
|
||||
i32::try_from(*self).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for i32 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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<i32> {
|
||||
Some(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for u32 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
(*self as u64).format(spec)
|
||||
}
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
i32::try_from(*self).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for i16 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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<i32> {
|
||||
Some(*self as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for u16 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
(*self as u64).format(spec)
|
||||
}
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
Some(*self as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for i8 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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<i32> {
|
||||
Some(*self as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for u8 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
(*self as u64).format(spec)
|
||||
}
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
Some(*self as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for usize {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
(*self as u64).format(spec)
|
||||
}
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
i32::try_from(*self).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for isize {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
(*self as u64).format(spec)
|
||||
}
|
||||
fn as_int(&self) -> Option<i32> {
|
||||
i32::try_from(*self).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for f64 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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<i32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for f32 {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
(*self as f64).format(spec)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for &wstr {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
if spec.conversion_type == ConversionType::String {
|
||||
Ok((*self).to_owned())
|
||||
} else {
|
||||
Err(PrintfError::WrongType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for &str {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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<WString> {
|
||||
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<WString> {
|
||||
self.as_str().format(spec)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for WString {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
self.as_utfstr().format(spec)
|
||||
}
|
||||
}
|
||||
|
||||
impl Printf for &WString {
|
||||
fn format(&self, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
self.as_utfstr().format(spec)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_padding(mut s: WString, spec: &ConversionSpecifier) -> Result<WString> {
|
||||
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)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#[allow(clippy::module_inception)]
|
||||
mod format;
|
||||
mod parser;
|
||||
pub mod printf;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
|
@ -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<Vec<FormatElement>> {
|
||||
// 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),
|
||||
}
|
||||
}
|
|
@ -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<T> = std::result::Result<T, PrintfError>;
|
||||
|
||||
/// 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<WString> {
|
||||
vsprintfp(&parse_format_string(format)?, args)
|
||||
}
|
||||
|
||||
fn vsprintfp(format: &[FormatElement], args: &[&dyn Printf]) -> Result<WString> {
|
||||
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!");
|
||||
}
|
||||
}
|
|
@ -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<T: Printf>(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<T: Printf, T2: Printf>(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);
|
||||
}
|
|
@ -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();
|
||||
|
|
16
fish-rust/src/wutil/printf.rs
Normal file
16
fish-rust/src/wutil/printf.rs
Normal file
|
@ -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!");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue