fmt::{Display, Error as FmtError, Formatter, Result as FmtResult, Write},
io::Write as _,
ops::{Not, RangeInclusive},
- str::{from_utf8_unchecked, FromStr},
+ str::{from_utf8_unchecked, Chars, FromStr},
sync::LazyLock,
};
}
}
+impl TryFrom<char> for Decimal {
+ type Error = ();
+
+ fn try_from(c: char) -> Result<Self, Self::Error> {
+ match c {
+ '.' => Ok(Self::Dot),
+ ',' => Ok(Self::Comma),
+ _ => Err(()),
+ }
+ }
+}
+
impl Not for Decimal {
type Output = Self;
}
impl Affix {
- fn new(s: &str) -> Self {
+ fn new(s: impl Into<String>) -> Self {
+ let s = s.into();
Self {
- s: s.to_string(),
width: s.width(),
+ s,
}
}
+
+ fn extra_bytes(&self) -> usize {
+ self.s.len().checked_sub(self.width).unwrap()
+ }
}
-pub struct DisplayValue<'a> {
+impl FromStr for NumberStyle {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ fn find_separator(s: &str) -> Option<char> {
+ // Count commas and periods. There must be exactly three of one or
+ // the other, except that an apostrophe escapes a following comma or
+ // period.
+ let mut n_commas = 0;
+ let mut n_periods = 0;
+ let s = s.as_bytes();
+ for i in 0..s.len() {
+ if i > 0 && s[i - 1] == b'\'' {
+ } else if s[i] == b',' {
+ n_commas += 1;
+ } else if s[i] == b'.' {
+ n_periods += 1;
+ }
+ }
+
+ if n_commas == 3 && n_periods != 3 {
+ Some(',')
+ } else if n_periods == 3 && n_commas != 3 {
+ Some('.')
+ } else {
+ None
+ }
+ }
+
+ fn take_cc_token(iter: &mut Chars<'_>, grouping: char) -> Affix {
+ let mut s = String::new();
+ let mut quote = false;
+ for c in iter {
+ if c == '\'' {
+ quote = true;
+ } else {
+ if c == grouping && !quote {
+ break;
+ }
+ s.push(c);
+ quote = false;
+ }
+ }
+ Affix::new(s)
+ }
+
+ let Some(grouping) = find_separator(s) else {
+ return Err(());
+ };
+ let mut iter = s.chars();
+ let neg_prefix = take_cc_token(&mut iter, grouping);
+ let prefix = take_cc_token(&mut iter, grouping);
+ let suffix = take_cc_token(&mut iter, grouping);
+ let neg_suffix = take_cc_token(&mut iter, grouping);
+ let grouping: Decimal = grouping.try_into().unwrap();
+ let decimal = !grouping;
+ let extra_bytes = neg_prefix.extra_bytes()
+ + prefix.extra_bytes()
+ + suffix.extra_bytes()
+ + neg_suffix.extra_bytes();
+ Ok(Self {
+ neg_prefix,
+ prefix,
+ suffix,
+ neg_suffix,
+ decimal,
+ grouping: Some(grouping),
+ leading_zero: false,
+ extra_bytes,
+ })
+ }
+}
+
+pub struct DisplayValue<'a, 'b> {
format: Format,
+ settings: &'b Settings,
value: &'a Value,
encoding: &'static Encoding,
}
-impl<'a> Display for DisplayValue<'a> {
+impl<'a, 'b> Display for DisplayValue<'a, 'b> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let number = match self.value {
Value::Number(number) => *number,
}
}
-impl<'a> DisplayValue<'a> {
+impl<'a, 'b> DisplayValue<'a, 'b> {
pub fn new(format: Format, value: &'a Value, encoding: &'static Encoding) -> Self {
Self {
format,
value,
encoding,
+ settings: &PsppSettings::global().formats,
}
}
+ pub fn with_settings(self, settings: &'b Settings) -> Self {
+ Self { settings, ..self }
+ }
fn number(&self, f: &mut Formatter<'_>, number: f64) -> FmtResult {
if number.is_finite() {
- let style = PsppSettings::global()
- .formats
- .number_style(self.format.type_);
+ let style = self.settings.number_style(self.format.type_);
if self.format.type_ != Type::E && number.abs() < 1.5 * power10(self.format.w()) {
let rounder = Rounder::new(style, number, self.format.d);
if self.decimal(f, &rounder, style, true)?
}
}
b'y' => {
- let epoch = PsppSettings::global().formats.epoch.0;
+ let epoch = self.settings.epoch.0;
let offset = date.year() - epoch;
if offset < 0 || offset > 99 {
return self.overflow(f);
let d = min(self.format.d(), excess_width as usize);
let w = d + 3;
write!(&mut output, ":{:02$.*}", d, number, w).unwrap();
- if PsppSettings::global().formats.decimal == Decimal::Comma {
+ if self.settings.decimal == Decimal::Comma {
fix_decimal_point(&mut output);
}
}
use crate::{
dictionary::Value,
- format::{AbstractFormat, Format, UncheckedFormat},
+ format::{AbstractFormat, Format, Settings, UncheckedFormat},
lex::{
scan::StringScanner,
segment::Syntax,
.join("src/format/testdata")
.join(name);
let input = BufReader::new(File::open(&filename).unwrap());
+ let settings = Settings::default();
let mut value = 0.0;
let mut value_name = String::new();
for (line_number, line) in input.lines().map(|r| r.unwrap()).enumerate() {