generalize datum, without yet getting rid of dat
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Jul 2025 01:48:51 +0000 (18:48 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 25 Jul 2025 01:48:51 +0000 (18:48 -0700)
rust/pspp/src/data.rs
rust/pspp/src/dictionary.rs
rust/pspp/src/format/mod.rs
rust/pspp/src/format/parse.rs
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/sys/cooked.rs
rust/pspp/src/sys/raw.rs
rust/pspp/src/sys/raw/records.rs
rust/pspp/src/sys/write.rs

index b85033c4d72357f57fdf0d5ceb6ab78205762444..883060ef82667d9cfbcb96c5a6202e6b53feae3b 100644 (file)
@@ -271,7 +271,7 @@ pub enum EncodedDatum {
 }
 
 impl EncodedDatum {
-    pub fn into_raw(self) -> Datum {
+    pub fn into_raw(self) -> Datum<RawString> {
         match self {
             EncodedDatum::Number(number) => Datum::Number(number),
             EncodedDatum::String(encoded_string) => Datum::String(encoded_string.into()),
@@ -572,7 +572,10 @@ impl Dat<'_> {
 
 /// The value of a [Variable](crate::dictionary::Variable).
 #[derive(Clone)]
-pub enum Datum {
+pub enum Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     /// A numeric value.
     Number(
         /// A number, or `None` for the system-missing value.
@@ -581,11 +584,14 @@ pub enum Datum {
     /// A string value.
     String(
         /// The value, in the variable's encoding.
-        RawString,
+        B,
     ),
 }
 
-impl Debug for Datum {
+impl<B> Debug for Datum<B>
+where
+    B: Borrow<RawStr> + Debug,
+{
     fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
         match self {
             Self::Number(Some(number)) => write!(f, "{number:?}"),
@@ -595,7 +601,10 @@ impl Debug for Datum {
     }
 }
 
-impl Serialize for Datum {
+impl<B> Serialize for Datum<B>
+where
+    B: Borrow<RawStr> + Serialize,
+{
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: serde::Serializer,
@@ -607,28 +616,37 @@ impl Serialize for Datum {
     }
 }
 
-impl PartialEq for Datum {
+impl<B> PartialEq for Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     fn eq(&self, other: &Self) -> bool {
         match (self, other) {
             (Self::Number(Some(l0)), Self::Number(Some(r0))) => {
                 OrderedFloat(*l0) == OrderedFloat(*r0)
             }
             (Self::Number(None), Self::Number(None)) => true,
-            (Self::String(l0), Self::String(r0)) => l0 == r0,
+            (Self::String(l0), Self::String(r0)) => l0.borrow() == r0.borrow(),
             _ => false,
         }
     }
 }
 
-impl Eq for Datum {}
+impl<B> Eq for Datum<B> where B: Borrow<RawStr> {}
 
-impl PartialOrd for Datum {
+impl<B> PartialOrd for Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
         Some(self.cmp(other))
     }
 }
 
-impl Ord for Datum {
+impl<B> Ord for Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     fn cmp(&self, other: &Self) -> Ordering {
         match (self, other) {
             (Self::Number(a), Self::Number(b)) => match (a, b) {
@@ -639,21 +657,24 @@ impl Ord for Datum {
             },
             (Self::Number(_), Self::String(_)) => Ordering::Less,
             (Self::String(_), Self::Number(_)) => Ordering::Greater,
-            (Self::String(a), Self::String(b)) => a.cmp(b),
+            (Self::String(a), Self::String(b)) => a.borrow().cmp(b.borrow()),
         }
     }
 }
 
-impl Hash for Datum {
+impl<B> Hash for Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
         match self {
             Self::Number(number) => number.map(OrderedFloat).hash(state),
-            Self::String(string) => string.hash(state),
+            Self::String(string) => string.borrow().hash(state),
         }
     }
 }
 
-impl Datum {
+impl Datum<RawString> {
     /// Constructs a new numerical [Datum] for the system-missing value.
     pub const fn sysmis() -> Self {
         Self::Number(None)
@@ -742,7 +763,7 @@ impl Datum {
     /// Compares this datum and `other` for equality, ignoring trailing ASCII
     /// spaces in either, if they are both strings, for the purpose of
     /// comparison.
-    pub fn eq_ignore_trailing_spaces(&self, other: &Datum) -> bool {
+    pub fn eq_ignore_trailing_spaces<B2>(&self, other: &Datum<RawString>) -> bool {
         match (self, other) {
             (Self::String(a), Self::String(b)) => a.eq_ignore_trailing_spaces(b),
             _ => self == other,
@@ -765,25 +786,37 @@ impl Datum {
     }
 }
 
-impl From<f64> for Datum {
+impl<B> From<f64> for Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     fn from(number: f64) -> Self {
         Some(number).into()
     }
 }
 
-impl From<Option<f64>> for Datum {
+impl<B> From<Option<f64>> for Datum<B>
+where
+    B: Borrow<RawStr>,
+{
     fn from(value: Option<f64>) -> Self {
         Self::Number(value)
     }
 }
 
-impl From<&str> for Datum {
+impl<B> From<&str> for Datum<B>
+where
+    B: Borrow<RawStr> + for<'a> From<&'a [u8]>,
+{
     fn from(value: &str) -> Self {
         value.as_bytes().into()
     }
 }
 
-impl From<&[u8]> for Datum {
+impl<B> From<&[u8]> for Datum<B>
+where
+    B: Borrow<RawStr> + for<'a> From<&'a [u8]>,
+{
     fn from(value: &[u8]) -> Self {
         Self::String(value.into())
     }
@@ -796,17 +829,17 @@ pub struct RawCase(
     /// order.
     ///
     /// [Dictionary]: crate::dictionary::Dictionary
-    pub Vec<Datum>,
+    pub Vec<Datum<RawString>>,
 );
 
 impl RawCase {
-    pub fn as_encoding(&self, encoding: &'static Encoding) -> Case<&'_ [Datum]> {
+    pub fn as_encoding(&self, encoding: &'static Encoding) -> Case<&'_ [Datum<RawString>]> {
         Case {
             encoding,
             data: &self.0,
         }
     }
-    pub fn with_encoding(self, encoding: &'static Encoding) -> Case<Vec<Datum>> {
+    pub fn with_encoding(self, encoding: &'static Encoding) -> Case<Vec<Datum<RawString>>> {
         Case {
             encoding,
             data: self.0,
@@ -816,7 +849,7 @@ impl RawCase {
 
 pub struct Case<B>
 where
-    B: Borrow<[Datum]>,
+    B: Borrow<[Datum<RawString>]>,
 {
     encoding: &'static Encoding,
     data: B,
@@ -824,14 +857,14 @@ where
 
 impl<B> Case<B>
 where
-    B: Borrow<[Datum]>,
+    B: Borrow<[Datum<RawString>]>,
 {
     fn len(&self) -> usize {
         self.data.borrow().len()
     }
 }
 
-impl IntoIterator for Case<Vec<Datum>> {
+impl IntoIterator for Case<Vec<Datum<RawString>>> {
     type Item = EncodedDatum;
 
     type IntoIter = CaseVecIter;
@@ -846,7 +879,7 @@ impl IntoIterator for Case<Vec<Datum>> {
 
 pub struct CaseVecIter {
     encoding: &'static Encoding,
-    iter: std::vec::IntoIter<Datum>,
+    iter: std::vec::IntoIter<Datum<RawString>>,
 }
 
 impl Iterator for CaseVecIter {
index 30a31f915e912af75a6186a3fe5843221592b6ac..1d4f9201f2c5e5e72782914789377ab13d3cd05b 100644 (file)
@@ -40,7 +40,7 @@ use thiserror::Error as ThisError;
 use unicase::UniCase;
 
 use crate::{
-    data::{Datum, EncodedDat, EncodedDatum},
+    data::{Datum, EncodedDat, EncodedDatum, RawString},
     format::{DisplayPlain, Format},
     identifier::{ByIdentifier, HasIdentifier, Identifier},
     output::pivot::{
@@ -1845,7 +1845,7 @@ pub enum MultipleResponseType {
     /// one value (the "counted value") means that the box was checked, and any
     /// other value means that it was not.
     MultipleDichotomy {
-        datum: Datum,
+        datum: Datum<RawString>,
         labels: CategoryLabels,
     },
 
@@ -1903,7 +1903,7 @@ impl DictIndexVariableSet {
 }
 
 #[derive(Clone, Default, PartialEq, Eq, Serialize)]
-pub struct ValueLabels(pub HashMap<Datum, String>);
+pub struct ValueLabels(pub HashMap<Datum<RawString>, String>);
 
 impl ValueLabels {
     pub fn new() -> Self {
@@ -1914,11 +1914,11 @@ impl ValueLabels {
         self.0.is_empty()
     }
 
-    pub fn get(&self, datum: &Datum) -> Option<&str> {
+    pub fn get(&self, datum: &Datum<RawString>) -> Option<&str> {
         self.0.get(datum).map(|s| s.as_str())
     }
 
-    pub fn insert(&mut self, datum: Datum, label: String) -> Option<String> {
+    pub fn insert(&mut self, datum: Datum<RawString>, label: String) -> Option<String> {
         self.0.insert(datum, label)
     }
 
index 75bac4038aaea2918817e7c2018b1e1ba1c91884..ac7c98f213da03c34fabdd96cedbdf0fc7f9ac6f 100644 (file)
@@ -393,7 +393,7 @@ impl Type {
         }
     }
 
-    pub fn default_value(&self) -> Datum {
+    pub fn default_value(&self) -> Datum<RawString> {
         match self.var_type() {
             VarType::Numeric => Datum::sysmis(),
             VarType::String => Datum::String(RawString::default()),
@@ -621,7 +621,7 @@ impl Format {
         Ok(self)
     }
 
-    pub fn default_value(&self) -> Datum {
+    pub fn default_value(&self) -> Datum<RawString> {
         match self.var_width() {
             VarWidth::Numeric => Datum::sysmis(),
             VarWidth::String(width) => Datum::String(RawString::spaces(width as usize)),
index 495037938bb7218135a1706a79015ed16bf74a08..e423dc98cf6b77771b660cf79f44dccbe8128fcc 100644 (file)
@@ -16,7 +16,7 @@
 
 use crate::{
     calendar::{calendar_gregorian_to_offset, DateError},
-    data::{Datum, EncodedStr, EncodedString},
+    data::{Datum, EncodedStr, EncodedString, RawString},
     endian::{Endian, Parse},
     format::{DateTemplate, Decimals, Settings, TemplateItem, Type},
     settings::{EndianSettings, Settings as PsppSettings},
@@ -190,7 +190,7 @@ impl<'a> ParseValue<'a> {
     /// input into UTF-8, but this will screw up parsing of binary formats,
     /// because recoding bytes from (e.g.) windows-1252 into UTF-8, and then
     /// interpreting them as a binary number yields nonsense.
-    pub fn parse<'b, T>(&self, input: T) -> Result<Datum, ParseError>
+    pub fn parse<'b, T>(&self, input: T) -> Result<Datum<RawString>, ParseError>
     where
         T: Into<EncodedStr<'b>>,
     {
@@ -239,7 +239,7 @@ impl<'a> ParseValue<'a> {
         })
     }
 
-    fn parse_number(&self, input: &str, type_: Type) -> Result<Datum, ParseErrorKind> {
+    fn parse_number(&self, input: &str, type_: Type) -> Result<Datum<RawString>, ParseErrorKind> {
         let style = self.settings.number_style(type_);
 
         let input = input.trim();
@@ -312,14 +312,14 @@ impl<'a> ParseValue<'a> {
         }
     }
 
-    fn parse_n(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_n(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         match input.chars().find(|c| !c.is_ascii_digit()) {
             None => Ok(Datum::Number(Some(input.parse().unwrap()))),
             Some(nondigit) => Err(ParseErrorKind::Nondigit(nondigit)),
         }
     }
 
-    fn parse_z(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_z(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         let input = input.trim();
         if input.is_empty() || input == "." {
             return Ok(Datum::sysmis());
@@ -396,12 +396,12 @@ impl<'a> ParseValue<'a> {
         }
     }
 
-    fn parse_pk(&self, input: &[u8]) -> Result<Datum, ParseErrorKind> {
+    fn parse_pk(&self, input: &[u8]) -> Result<Datum<RawString>, ParseErrorKind> {
         let number = Self::parse_bcd(input)?;
         Ok(Datum::Number(Some(self.apply_decimals(number as f64))))
     }
 
-    fn parse_p(&self, input: &[u8]) -> Result<Datum, ParseErrorKind> {
+    fn parse_p(&self, input: &[u8]) -> Result<Datum<RawString>, ParseErrorKind> {
         if input.is_empty() {
             return Ok(Datum::Number(None));
         };
@@ -423,7 +423,7 @@ impl<'a> ParseValue<'a> {
         }
     }
 
-    fn parse_ib(&self, input: &[u8]) -> Result<Datum, ParseErrorKind> {
+    fn parse_ib(&self, input: &[u8]) -> Result<Datum<RawString>, ParseErrorKind> {
         let number = self.parse_binary(input);
         let sign_bit = 1 << (input.len() * 8 - 1);
         let number = if (number & sign_bit) == 0 {
@@ -434,12 +434,12 @@ impl<'a> ParseValue<'a> {
         Ok(Datum::Number(Some(self.apply_decimals(number as f64))))
     }
 
-    fn parse_pib(&self, input: &[u8]) -> Result<Datum, ParseErrorKind> {
+    fn parse_pib(&self, input: &[u8]) -> Result<Datum<RawString>, ParseErrorKind> {
         let number = self.parse_binary(input);
         Ok(Datum::Number(Some(self.apply_decimals(number as f64))))
     }
 
-    fn parse_rb(&self, input: &[u8]) -> Result<Datum, ParseErrorKind> {
+    fn parse_rb(&self, input: &[u8]) -> Result<Datum<RawString>, ParseErrorKind> {
         let mut bytes = [0; 8];
         let len = input.len().min(8);
         bytes[..len].copy_from_slice(&input[..len]);
@@ -453,7 +453,7 @@ impl<'a> ParseValue<'a> {
         Ok(Datum::Number(number))
     }
 
-    fn parse_ahex(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_ahex(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         let mut result = Vec::with_capacity(input.len() / 2);
         let mut iter = input.chars();
         while let Some(hi) = iter.next() {
@@ -483,17 +483,17 @@ impl<'a> ParseValue<'a> {
         }
     }
 
-    fn parse_pibhex(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_pibhex(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         self.parse_hex(input)
             .map(|value| Datum::Number(value.map(|number| number as f64)))
     }
 
-    fn parse_rbhex(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_rbhex(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         self.parse_hex(input)
             .map(|value| Datum::Number(value.map(f64::from_bits)))
     }
 
-    fn parse_date(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_date(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         let mut p = StrParser(input.trim());
         if p.0.is_empty() || p.0 == "." {
             return Ok(Datum::sysmis());
@@ -609,7 +609,7 @@ impl<'a> ParseValue<'a> {
         Ok(time + seconds)
     }
 
-    fn parse_wkday(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_wkday(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         let mut p = StrParser(input.trim());
         if p.0.is_empty() || p.0 == "." {
             Ok(Datum::sysmis())
@@ -620,7 +620,7 @@ impl<'a> ParseValue<'a> {
         }
     }
 
-    fn parse_month(&self, input: &str) -> Result<Datum, ParseErrorKind> {
+    fn parse_month(&self, input: &str) -> Result<Datum<RawString>, ParseErrorKind> {
         let mut p = StrParser(input.trim());
         if p.0.is_empty() || p.0 == "." {
             Ok(Datum::sysmis())
index 8260b95488ace76bec45343ddeb25b4638b52814..7d9732261d438d47aa052d513ff54220c68c5328 100644 (file)
@@ -68,7 +68,7 @@ use thiserror::Error as ThisError;
 use tlo::parse_tlo;
 
 use crate::{
-    data::{Datum, EncodedDat, EncodedDatum},
+    data::{Datum, EncodedDat, EncodedDatum, RawString},
     dictionary::{VarType, Variable},
     format::{Decimal, Format, Settings as FormatSettings, Type, UncheckedFormat},
     settings::{Settings, Show},
@@ -1866,7 +1866,7 @@ impl Value {
             EncodedDat::String(string) => Self::new_user_text(string.as_str()),
         }
     }
-    pub fn new_variable_value(variable: &Variable, value: &Datum) -> Self {
+    pub fn new_variable_value(variable: &Variable, value: &Datum<RawString>) -> Self {
         let var_name = Some(variable.name.as_str().into());
         let value_label = variable.value_labels.get(value).map(String::from);
         match value {
index 22662678e559062bf78328eb0c273fd904a95969..778bc778e2ebb0f9b35dc08ae336a9590db52375 100644 (file)
@@ -1695,7 +1695,7 @@ impl Debug for Cases {
 }
 
 impl Iterator for Cases {
-    type Item = Result<Case<Vec<Datum>>, raw::Error>;
+    type Item = Result<Case<Vec<Datum<RawString>>>, raw::Error>;
 
     fn next(&mut self) -> Option<Self::Item> {
         self.inner
index 27ffb2f8109455d59eadda93f19d219ee46bed76..f330a0a5d4f207026c9a69b4c89266a2b2ba0a25 100644 (file)
@@ -930,7 +930,7 @@ impl RawDatum {
 
     /// Decodes a `RawDatum` into a [Datum] given that we now know the string
     /// width.
-    pub fn decode(&self, width: VarWidth) -> Datum {
+    pub fn decode(&self, width: VarWidth) -> Datum<RawString> {
         match self {
             Self::Number(x) => Datum::Number(*x),
             Self::String(s) => {
@@ -941,7 +941,7 @@ impl RawDatum {
     }
 }
 
-impl Datum {
+impl Datum<RawString> {
     fn read_case<R: Read + Seek>(
         reader: &mut R,
         case_vars: &[CaseVar],
index e46e86e2a09b5f63b52a5c074875b9b7d6872dde..c418c8f5707ed852f0b957ab7a0e5925811dfb1b 100644 (file)
@@ -355,14 +355,14 @@ fn format_name(type_: u32) -> Cow<'static, str> {
 #[derive(Clone, Debug, Default, Serialize)]
 pub struct RawMissingValues {
     /// Individual missing values, up to 3 of them.
-    pub values: Vec<Datum>,
+    pub values: Vec<Datum<RawString>>,
 
     /// Optional range of missing values.
     pub range: Option<MissingValueRange>,
 }
 
 impl RawMissingValues {
-    pub fn new(values: Vec<Datum>, range: Option<MissingValueRange>) -> Self {
+    pub fn new(values: Vec<Datum<RawString>>, range: Option<MissingValueRange>) -> Self {
         Self { values, range }
     }
 
index e42f1c5066f564063a617ee1b7649eb4983cc7ef..785c6cb24219a9136c010f514f76153c7a5e7b90 100644 (file)
@@ -1,5 +1,5 @@
 use std::{
-    borrow::Cow,
+    borrow::{Borrow, Cow},
     collections::HashMap,
     fmt::Write as _,
     fs::File,
@@ -17,7 +17,7 @@ use itertools::zip_eq;
 use smallvec::SmallVec;
 
 use crate::{
-    data::{Dat, Datum, EncodedDatum},
+    data::{Dat, Datum, EncodedDatum, RawStr, RawString},
     dictionary::{
         Alignment, Attributes, CategoryLabels, Dictionary, Measure, MultipleResponseType,
         ValueLabels, VarWidth,
@@ -674,7 +674,7 @@ impl BinWrite for Pad {
     }
 }
 
-impl BinWrite for Datum {
+impl BinWrite for Datum<RawString> {
     type Args<'a> = ();
 
     fn write_options<W: Write + Seek>(
@@ -842,7 +842,7 @@ where
 
     fn write_case_uncompressed<'c>(
         &mut self,
-        case: impl Iterator<Item = Datum>,
+        case: impl Iterator<Item = Datum<RawString>>,
     ) -> Result<(), BinError> {
         for (var, datum) in zip_eq(self.case_vars, case) {
             match var {
@@ -865,7 +865,7 @@ where
     }
     fn write_case_compressed<'c>(
         &mut self,
-        case: impl Iterator<Item = Datum>,
+        case: impl Iterator<Item = Datum<RawString>>,
     ) -> Result<(), BinError> {
         for (var, datum) in zip_eq(self.case_vars, case) {
             match var {
@@ -991,7 +991,7 @@ where
     /// Panics if [try_finish](Self::try_finish) has been called.
     pub fn write_case<'a>(
         &mut self,
-        case: impl IntoIterator<Item = Datum>,
+        case: impl IntoIterator<Item = Datum<RawString>>,
     ) -> Result<(), BinError> {
         match self.inner.as_mut().unwrap() {
             Either::Left(inner) => {