From 23a5f073d734821a835168a16fe077cf76833991 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 8 Jan 2026 14:30:24 -0800 Subject: [PATCH] work --- rust/pspp/src/format.rs | 228 ++++++++++++++++++-------------- rust/pspp/src/spv/read/light.rs | 36 ++--- 2 files changed, 147 insertions(+), 117 deletions(-) diff --git a/rust/pspp/src/format.rs b/rust/pspp/src/format.rs index c45da56aae..f180434b73 100644 --- a/rust/pspp/src/format.rs +++ b/rust/pspp/src/format.rs @@ -1013,6 +1013,10 @@ pub struct Settings { /// instead of `.5`)? pub leading_zero: bool, + /// Format `PCT` and `DOLLAR` with leading zero (e.g. `$0.5` instead of + /// `$.5`)? + pub leading_zero_pct: bool, + /// Custom currency styles. pub ccs: EnumMap>>, } @@ -1021,12 +1025,14 @@ pub struct Settings { struct StyleParams { decimal: Decimal, leading_zero: bool, + leading_zero_pct: bool, } impl From<&Settings> for StyleParams { fn from(value: &Settings) -> Self { Self { decimal: value.decimal, leading_zero: value.leading_zero, + leading_zero_pct: value.leading_zero_pct, } } } @@ -1042,6 +1048,48 @@ impl StyleSet { } } +struct NumberStyles { + f: StyleSet, + comma: StyleSet, + dot: StyleSet, + dollar: StyleSet, + pct: StyleSet, + default: NumberStyle, +} +impl NumberStyles { + fn new() -> Self { + Self { + f: StyleSet::new(|p| NumberStyle::new(p.decimal, p.leading_zero)), + comma: StyleSet::new(|p| { + NumberStyle::new(p.decimal, p.leading_zero).with_grouping(true) + }), + dot: StyleSet::new(|p| { + NumberStyle::new(!p.decimal, p.leading_zero).with_grouping(true) + }), + dollar: StyleSet::new(|p| { + NumberStyle::new(p.decimal, p.leading_zero_pct) + .with_grouping(true) + .with_prefix("$") + }), + pct: StyleSet::new(|p| { + NumberStyle::new(p.decimal, p.leading_zero_pct).with_suffix("%") + }), + default: NumberStyle::new(Decimal::Dot, false), + } + } + fn get<'a>(&'a self, settings: &'a Settings, type_: Type) -> &'a NumberStyle { + match type_ { + Type::F | Type::E => self.f.get(settings), + Type::Comma => self.comma.get(settings), + Type::Dot => self.dot.get(settings), + Type::Dollar => self.dollar.get(settings), + Type::Pct => self.pct.get(settings), + Type::CC(cc) => settings.ccs[cc].as_deref().unwrap_or(&self.default), + _ => &self.default, + } + } +} + impl Settings { pub fn with_cc(mut self, cc: CC, style: NumberStyle) -> Self { self.ccs[cc] = Some(Box::new(style)); @@ -1053,76 +1101,18 @@ impl Settings { ..self } } + pub fn with_leading_zero_pct(self, leading_zero_pct: bool) -> Self { + Self { + leading_zero_pct, + ..self + } + } pub fn with_epoch(self, epoch: Epoch) -> Self { Self { epoch, ..self } } pub fn number_style(&self, type_: Type) -> &NumberStyle { - static DEFAULT: LazyLock = - LazyLock::new(|| NumberStyle::new("", "", Decimal::Dot, None, false)); - - match type_ { - Type::F | Type::E => { - static F: LazyLock = LazyLock::new(|| { - StyleSet::new(|p| NumberStyle::new("", "", p.decimal, None, p.leading_zero)) - }); - F.get(self) - } - Type::Comma => { - static COMMA: LazyLock = LazyLock::new(|| { - StyleSet::new(|p| { - NumberStyle::new("", "", p.decimal, Some(!p.decimal), p.leading_zero) - }) - }); - COMMA.get(self) - } - Type::Dot => { - static DOT: LazyLock = LazyLock::new(|| { - StyleSet::new(|p| { - NumberStyle::new("", "", !p.decimal, Some(p.decimal), p.leading_zero) - }) - }); - DOT.get(self) - } - Type::Dollar => { - static DOLLAR: LazyLock = LazyLock::new(|| { - StyleSet::new(|p| NumberStyle::new("$", "", p.decimal, Some(!p.decimal), false)) - }); - DOLLAR.get(self) - } - Type::Pct => { - static PCT: LazyLock = LazyLock::new(|| { - StyleSet::new(|p| NumberStyle::new("", "%", p.decimal, None, false)) - }); - PCT.get(self) - } - Type::CC(cc) => self.ccs[cc].as_deref().unwrap_or(&DEFAULT), - Type::N - | Type::Z - | Type::P - | Type::PK - | Type::IB - | Type::PIB - | Type::PIBHex - | Type::RB - | Type::RBHex - | Type::Date - | Type::ADate - | Type::EDate - | Type::JDate - | Type::SDate - | Type::QYr - | Type::MoYr - | Type::WkYr - | Type::DateTime - | Type::YmdHms - | Type::MTime - | Type::Time - | Type::DTime - | Type::WkDay - | Type::Month - | Type::A - | Type::AHex => &DEFAULT, - } + static NUMBER_STYLES: LazyLock = LazyLock::new(|| NumberStyles::new()); + NUMBER_STYLES.get(self, type_) } } @@ -1178,29 +1168,64 @@ impl Display for NumberStyle { } impl NumberStyle { - fn new( - prefix: &str, - suffix: &str, - decimal: Decimal, - grouping: Option, - leading_zero: bool, - ) -> Self { - // These assertions ensure that zero is correct for `extra_bytes`. - debug_assert!(prefix.is_ascii()); - debug_assert!(suffix.is_ascii()); - + fn new(decimal: Decimal, leading_zero: bool) -> Self { Self { - neg_prefix: Affix::new("-"), - prefix: Affix::new(prefix), - suffix: Affix::new(suffix), - neg_suffix: Affix::new(""), + neg_prefix: Affix::from("-"), + prefix: Affix::new(), + suffix: Affix::new(), + neg_suffix: Affix::new(), decimal, - grouping, + grouping: None, leading_zero, extra_bytes: 0, } } + fn with_grouping(self, grouping: bool) -> Self { + Self { + grouping: grouping.then_some(!self.decimal), + ..self + } + } + + fn with_prefix(self, prefix: impl Into) -> Self { + let prefix = Affix::from(prefix); + Self { + extra_bytes: self.extra_bytes - self.prefix.extra_bytes() + prefix.extra_bytes(), + prefix, + ..self + } + } + + fn with_neg_prefix(self, neg_prefix: impl Into) -> Self { + let neg_prefix = Affix::from(neg_prefix); + Self { + extra_bytes: self.extra_bytes - self.neg_prefix.extra_bytes() + + neg_prefix.extra_bytes(), + neg_prefix, + ..self + } + } + + fn with_neg_suffix(self, neg_suffix: impl Into) -> Self { + let neg_suffix = Affix::from(neg_suffix); + Self { + extra_bytes: self.extra_bytes - self.neg_suffix.extra_bytes() + + neg_suffix.extra_bytes(), + neg_suffix, + ..self + } + } + + fn with_suffix(self, suffix: impl Into) -> Self { + let suffix = Affix::from(suffix); + Self { + extra_bytes: self.extra_bytes - self.suffix.extra_bytes() + suffix.extra_bytes(), + suffix, + ..self + } + } + fn affix_width(&self) -> usize { self.prefix.width + self.suffix.width } @@ -1216,14 +1241,23 @@ pub struct Affix { pub width: usize, } -impl Affix { - fn new(s: impl Into) -> Self { - let s = s.into(); +impl From for Affix +where + T: Into, +{ + fn from(value: T) -> Self { + let s = value.into(); Self { width: s.width(), s, } } +} + +impl Affix { + fn new() -> Self { + Self::from("") + } fn extra_bytes(&self) -> usize { self.s.len().checked_sub(self.width).unwrap() @@ -1283,7 +1317,7 @@ impl FromStr for NumberStyle { } } - fn take_cc_token(iter: &mut Chars<'_>, grouping: char) -> Affix { + fn take_cc_token(iter: &mut Chars<'_>, grouping: char) -> String { let mut s = String::new(); let mut quote = false; for c in iter { @@ -1296,7 +1330,7 @@ impl FromStr for NumberStyle { quote = false; } } - Affix::new(s) + s } let Some(grouping) = find_separator(s) else { @@ -1309,20 +1343,12 @@ impl FromStr for NumberStyle { 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, - }) + Ok(Self::new(decimal, false) + .with_grouping(true) + .with_prefix(prefix) + .with_neg_prefix(neg_prefix) + .with_neg_suffix(neg_suffix) + .with_suffix(suffix)) } } diff --git a/rust/pspp/src/spv/read/light.rs b/rust/pspp/src/spv/read/light.rs index b2d7638b3b..0c627fed0b 100644 --- a/rust/pspp/src/spv/read/light.rs +++ b/rust/pspp/src/spv/read/light.rs @@ -179,11 +179,10 @@ impl LightTable { .cells .iter() .map(|cell| { - (PrecomputedIndex(cell.index as usize), { - let value = cell.value.decode(encoding, &footnotes, warn); - dbg!(value.inner.as_datum_value()); - value - }) + ( + PrecomputedIndex(cell.index as usize), + cell.value.decode(encoding, &footnotes, warn), + ) }) .collect::>(); let dimensions = self @@ -231,7 +230,12 @@ impl LightTable { settings: Arc::new(Settings { epoch: self.formats.y0.epoch(), decimal: self.formats.y0.decimal(warn), - leading_zero: y1.map_or(false, |y1| y1.include_leading_zero), + leading_zero: if let Some(y1) = y1 { + y1.include_leading_zero + } else { + false + }, + leading_zero_pct: true, ccs: self.formats.custom_currency.decode(encoding, warn), }), grouping: { @@ -865,10 +869,13 @@ struct Formats { impl Formats { fn y1(&self) -> Option<&Y1> { - self.v1 - .as_ref() - .map(|n0| &n0.y1) - .or_else(|| self.v3.as_ref().map(|v3| &v3.n3.y1)) + if let Some(n0) = &**self.v1 { + Some(&n0.y1) + } else if let Some(v3) = &self.v3 { + Some(&v3.n3.y1) + } else { + None + } } fn n1(&self) -> Option<&N1> { @@ -1325,9 +1332,8 @@ impl ValueNumber { footnotes: &pivot::Footnotes, warn: &mut dyn FnMut(LightWarning), ) -> value::Value { - let format = dbg!(self.format.decode(warn)); value::Value::new_number((self.x != -f64::MAX).then_some(self.x)) - .with_format(format) + .with_format(self.format.decode(warn)) .with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes)) } } @@ -1339,9 +1345,8 @@ impl ValueVarNumber { footnotes: &pivot::Footnotes, warn: &mut dyn FnMut(LightWarning), ) -> value::Value { - let format = self.format.decode(warn); value::Value::new_number((self.x != -f64::MAX).then_some(self.x)) - .with_format(format) + .with_format(self.format.decode(warn)) .with_styling(ValueMods::decode_optional(&self.mods, encoding, footnotes)) .with_value_label(self.value_label.decode_optional(encoding)) .with_variable_name(Some(self.var_name.decode(encoding))) @@ -1368,10 +1373,9 @@ impl ValueString { footnotes: &pivot::Footnotes, warn: &mut dyn FnMut(LightWarning), ) -> value::Value { - let format = self.format.decode(warn); value::Value::new(pivot::value::ValueInner::Datum(DatumValue { datum: Datum::new_utf8(self.s.decode(encoding)), - format, + format: self.format.decode(warn), show: self.show.decode(warn), variable: self.var_name.decode_optional(encoding), value_label: self.value_label.decode_optional(encoding), -- 2.30.2