From 33444c5f8c32e61e18bc98520dabeec07ad37849 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 12 Jul 2025 16:43:21 -0700 Subject: [PATCH] more cleanup --- rust/pspp/src/dictionary.rs | 260 ++++++++++++++++++++++++- rust/pspp/src/sys/cooked.rs | 10 +- rust/pspp/src/sys/raw.rs | 369 +++++++++--------------------------- 3 files changed, 352 insertions(+), 287 deletions(-) diff --git a/rust/pspp/src/dictionary.rs b/rust/pspp/src/dictionary.rs index 854c6dd66d..c555b30abf 100644 --- a/rust/pspp/src/dictionary.rs +++ b/rust/pspp/src/dictionary.rs @@ -36,11 +36,11 @@ use thiserror::Error as ThisError; use unicase::UniCase; use crate::{ - format::Format, + format::{DisplayPlain, Format}, identifier::{ByIdentifier, HasIdentifier, Identifier}, output::pivot::{Axis3, Dimension, Footnote, Footnotes, Group, PivotTable, Value}, settings::Show, - sys::raw::{Alignment, CategoryLabels, Measure, MissingValues, RawString, VarType}, + sys::raw::{CategoryLabels, RawString, VarType}, }; /// An index within [Dictionary::variables]. @@ -1406,6 +1406,262 @@ impl ValueLabels { } } +#[derive(Clone, Default)] +pub struct MissingValues { + /// Individual missing values, up to 3 of them. + values: Vec, + + /// Optional range of missing values. + range: Option, +} + +impl Debug for MissingValues { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + DisplayMissingValues { + mv: self, + encoding: None, + } + .fmt(f) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum MissingValuesError { + TooMany, + TooWide, + MixedTypes, +} + +impl MissingValues { + pub fn new( + mut values: Vec, + range: Option, + ) -> Result { + if values.len() > 3 { + return Err(MissingValuesError::TooMany); + } + + let mut var_type = None; + for value in values.iter_mut() { + value.trim_end(); + match value.width() { + VarWidth::String(w) if w > 8 => return Err(MissingValuesError::TooWide), + _ => (), + } + if var_type.is_some_and(|t| t != value.var_type()) { + return Err(MissingValuesError::MixedTypes); + } + var_type = Some(value.var_type()); + } + + if var_type == Some(VarType::String) && range.is_some() { + return Err(MissingValuesError::MixedTypes); + } + + Ok(Self { values, range }) + } + + pub fn is_empty(&self) -> bool { + self.values.is_empty() && self.range.is_none() + } + + pub fn var_type(&self) -> Option { + if let Some(datum) = self.values.first() { + Some(datum.var_type()) + } else if self.range.is_some() { + Some(VarType::Numeric) + } else { + None + } + } + + pub fn contains(&self, value: &Datum) -> bool { + if self + .values + .iter() + .any(|datum| datum.eq_ignore_trailing_spaces(value)) + { + return true; + } + + match value { + Datum::Number(Some(number)) => self.range.is_some_and(|range| range.contains(*number)), + _ => false, + } + } + + pub fn is_resizable(&self, width: VarWidth) -> bool { + self.values.iter().all(|datum| datum.is_resizable(width)) + && self.range.iter().all(|range| range.is_resizable(width)) + } + + pub fn resize(&mut self, width: VarWidth) { + for datum in &mut self.values { + datum.resize(width); + } + if let Some(range) = &mut self.range { + range.resize(width); + } + } + + pub fn display(&self, encoding: &'static Encoding) -> DisplayMissingValues<'_> { + DisplayMissingValues { + mv: self, + encoding: Some(encoding), + } + } +} + +pub struct DisplayMissingValues<'a> { + mv: &'a MissingValues, + encoding: Option<&'static Encoding>, +} + +impl<'a> Display for DisplayMissingValues<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + if let Some(range) = &self.mv.range { + write!(f, "{range}")?; + if !self.mv.values.is_empty() { + write!(f, "; ")?; + } + } + + for (i, value) in self.mv.values.iter().enumerate() { + if i > 0 { + write!(f, "; ")?; + } + match self.encoding { + Some(encoding) => value.display_plain(encoding).fmt(f)?, + None => value.fmt(f)?, + } + } + + if self.mv.is_empty() { + write!(f, "none")?; + } + Ok(()) + } +} + +#[derive(Copy, Clone)] +pub enum MissingValueRange { + In { low: f64, high: f64 }, + From { low: f64 }, + To { high: f64 }, +} + +impl MissingValueRange { + pub fn new(low: f64, high: f64) -> Self { + const LOWEST: f64 = f64::MIN.next_up(); + match (low, high) { + (f64::MIN | LOWEST, _) => Self::To { high }, + (_, f64::MAX) => Self::From { low }, + (_, _) => Self::In { low, high }, + } + } + + pub fn low(&self) -> Option { + match self { + MissingValueRange::In { low, .. } | MissingValueRange::From { low } => Some(*low), + MissingValueRange::To { .. } => None, + } + } + + pub fn high(&self) -> Option { + match self { + MissingValueRange::In { high, .. } | MissingValueRange::To { high } => Some(*high), + MissingValueRange::From { .. } => None, + } + } + + pub fn contains(&self, number: f64) -> bool { + match self { + MissingValueRange::In { low, high } => (*low..*high).contains(&number), + MissingValueRange::From { low } => number >= *low, + MissingValueRange::To { high } => number <= *high, + } + } + + pub fn is_resizable(&self, width: VarWidth) -> bool { + width.is_numeric() + } + + pub fn resize(&self, width: VarWidth) { + assert_eq!(width, VarWidth::Numeric); + } +} + +impl Display for MissingValueRange { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.low() { + Some(low) => low.display_plain().fmt(f)?, + None => write!(f, "LOW")?, + } + + write!(f, " THRU ")?; + + match self.high() { + Some(high) => high.display_plain().fmt(f)?, + None => write!(f, "HIGH")?, + } + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Alignment { + Left, + Right, + Center, +} + +impl Alignment { + pub fn default_for_type(var_type: VarType) -> Self { + match var_type { + VarType::Numeric => Self::Right, + VarType::String => Self::Left, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + Alignment::Left => "Left", + Alignment::Right => "Right", + Alignment::Center => "Center", + } + } +} + +/// [Level of measurement](https://en.wikipedia.org/wiki/Level_of_measurement). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Measure { + /// Nominal values can only be compared for equality. + Nominal, + + /// Ordinal values can be meaningfully ordered. + Ordinal, + + /// Scale values can be meaningfully compared for the degree of difference. + Scale, +} + +impl Measure { + pub fn default_for_type(var_type: VarType) -> Option { + match var_type { + VarType::Numeric => None, + VarType::String => Some(Self::Nominal), + } + } + + pub fn as_str(&self) -> &'static str { + match self { + Measure::Nominal => "Nominal", + Measure::Ordinal => "Ordinal", + Measure::Scale => "Scale", + } + } +} + #[cfg(test)] mod test { use std::collections::HashSet; diff --git a/rust/pspp/src/sys/cooked.rs b/rust/pspp/src/sys/cooked.rs index 5a68a2d43d..3b50a63ce5 100644 --- a/rust/pspp/src/sys/cooked.rs +++ b/rust/pspp/src/sys/cooked.rs @@ -19,8 +19,7 @@ use std::{collections::BTreeMap, ops::Range}; use crate::{ calendar::date_time_to_pspp, dictionary::{ - Datum, Dictionary, InvalidRole, MultipleResponseSet, MultipleResponseType, VarWidth, - Variable, VariableSet, + Datum, Dictionary, InvalidRole, MissingValues, MissingValuesError, MultipleResponseSet, MultipleResponseType, VarWidth, Variable, VariableSet }, endian::Endian, format::{Error as FormatError, Format, UncheckedFormat}, @@ -33,10 +32,9 @@ use crate::{ self, Cases, DecodedRecord, DocumentRecord, EncodingRecord, Extension, FileAttributesRecord, FloatInfoRecord, HeaderRecord, IntegerInfoRecord, LongName, LongNamesRecord, LongStringMissingValueRecord, LongStringValueLabelRecord, - MissingValues, MissingValuesError, MultipleResponseRecord, NumberOfCasesRecord, - ProductInfoRecord, RawDatum, RawString, RawWidth, ValueLabel, - ValueLabelRecord, VarDisplayRecord, VariableAttributesRecord, VariableRecord, - VariableSetRecord, VeryLongStringsRecord, ZHeader, ZTrailer, + MultipleResponseRecord, NumberOfCasesRecord, ProductInfoRecord, RawDatum, RawString, + RawWidth, ValueLabel, ValueLabelRecord, VarDisplayRecord, VariableAttributesRecord, + VariableRecord, VariableSetRecord, VeryLongStringsRecord, ZHeader, ZTrailer, }, }, }; diff --git a/rust/pspp/src/sys/raw.rs b/rust/pspp/src/sys/raw.rs index fb65892999..a1da804a6f 100644 --- a/rust/pspp/src/sys/raw.rs +++ b/rust/pspp/src/sys/raw.rs @@ -20,9 +20,11 @@ //! raw details. Most readers will want to use higher-level interfaces. use crate::{ - dictionary::{Attributes, Datum, VarWidth}, + dictionary::{ + Alignment, Attributes, Datum, Measure, MissingValueRange, MissingValues, VarWidth, + }, endian::{Endian, Parse, ToBytes}, - format::{DisplayPlain, DisplayPlainF64}, + format::DisplayPlainF64, identifier::{Error as IdError, Identifier}, sys::encoding::{default_encoding, get_encoding, Error as EncodingError}, }; @@ -66,16 +68,22 @@ pub enum Error { #[error("Invalid ZSAV compression code {0}")] InvalidZsavCompression(u32), - #[error("Document record at offset {offset:#x} has document line count ({n}) greater than the maximum number {max}.")] + #[error( + "Document record at offset {offset:#x} has document line count ({n}) greater than the maximum number {max}." + )] BadDocumentLength { offset: u64, n: usize, max: usize }, #[error("At offset {offset:#x}, unrecognized record type {rec_type}.")] BadRecordType { offset: u64, rec_type: u32 }, - #[error("In variable record starting at offset {start_offset:#x}, variable width is not in the valid range -1 to 255.")] + #[error( + "In variable record starting at offset {start_offset:#x}, variable width is not in the valid range -1 to 255." + )] BadVariableWidth { start_offset: u64, width: i32 }, - #[error("In variable record starting at offset {start_offset:#x}, variable label code {code} at offset {code_offset:#x} is not 0 or 1.")] + #[error( + "In variable record starting at offset {start_offset:#x}, variable label code {code} at offset {code_offset:#x} is not 0 or 1." + )] BadVariableLabelCode { start_offset: u64, code_offset: u64, @@ -93,16 +101,24 @@ pub enum Error { #[error("At offset {offset:#x}, string missing value code ({code}) is not 0, 1, 2, or 3.")] BadStringMissingValueCode { offset: u64, code: i32 }, - #[error("At offset {offset:#x}, number of value labels ({n}) is greater than the maximum number {max}.")] + #[error( + "At offset {offset:#x}, number of value labels ({n}) is greater than the maximum number {max}." + )] BadNumberOfValueLabels { offset: u64, n: u32, max: u32 }, - #[error("At offset {offset:#x}, following value label record, found record type {rec_type} instead of expected type 4 for variable index record")] + #[error( + "At offset {offset:#x}, following value label record, found record type {rec_type} instead of expected type 4 for variable index record" + )] ExpectedVarIndexRecord { offset: u64, rec_type: u32 }, - #[error("At offset {offset:#x}, number of variables indexes for value labels ({n}) is greater than the maximum number ({max}).")] + #[error( + "At offset {offset:#x}, number of variables indexes for value labels ({n}) is greater than the maximum number ({max})." + )] TooManyVarIndexes { offset: u64, n: u32, max: u32 }, - #[error("At offset {offset:#x}, record type 7 subtype {subtype} is too large with element size {size} and {count} elements.")] + #[error( + "At offset {offset:#x}, record type 7 subtype {subtype} is too large with element size {size} and {count} elements." + )] ExtensionRecordTooLarge { offset: u64, subtype: u32, @@ -110,7 +126,9 @@ pub enum Error { count: u32, }, - #[error("Unexpected end of file at offset {offset:#x}, {case_ofs} bytes into a {case_len}-byte case.")] + #[error( + "Unexpected end of file at offset {offset:#x}, {case_ofs} bytes into a {case_len}-byte case." + )] EofInCase { offset: u64, case_ofs: u64, @@ -129,10 +147,14 @@ pub enum Error { #[error("Data ends at offset {offset:#x}, {case_ofs} bytes into a compressed case.")] PartialCompressedCase { offset: u64, case_ofs: u64 }, - #[error("At {case_ofs} bytes into compressed case starting at offset {offset:#x}, a string was found where a number was expected.")] + #[error( + "At {case_ofs} bytes into compressed case starting at offset {offset:#x}, a string was found where a number was expected." + )] CompressedNumberExpected { offset: u64, case_ofs: u64 }, - #[error("At {case_ofs} bytes into compressed case starting at offset {offset:#x}, a number was found where a string was expected.")] + #[error( + "At {case_ofs} bytes into compressed case starting at offset {offset:#x}, a number was found where a string was expected." + )] CompressedStringExpected { offset: u64, case_ofs: u64 }, #[error("Impossible ztrailer_offset {0:#x}.")] @@ -156,7 +178,9 @@ pub enum Error { #[error("ZLIB trailer specifies unexpected {0}-byte block size.")] WrongZlibTrailerBlockSize(u32), - #[error("Block count {n_blocks} in ZLIB trailer at offset {offset:#x} differs from expected block count {expected_n_blocks} calculated from trailer length {ztrailer_len}.")] + #[error( + "Block count {n_blocks} in ZLIB trailer at offset {offset:#x} differs from expected block count {expected_n_blocks} calculated from trailer length {ztrailer_len}." + )] BadZlibTrailerNBlocks { offset: u64, n_blocks: u32, @@ -164,28 +188,36 @@ pub enum Error { ztrailer_len: u64, }, - #[error("ZLIB block descriptor {index} reported uncompressed data offset {actual:#x}, when {expected:#x} was expected.")] + #[error( + "ZLIB block descriptor {index} reported uncompressed data offset {actual:#x}, when {expected:#x} was expected." + )] ZlibTrailerBlockWrongUncmpOfs { index: usize, actual: u64, expected: u64, }, - #[error("ZLIB block descriptor {index} reported compressed data offset {actual:#x}, when {expected:#x} was expected.")] + #[error( + "ZLIB block descriptor {index} reported compressed data offset {actual:#x}, when {expected:#x} was expected." + )] ZlibTrailerBlockWrongCmpOfs { index: usize, actual: u64, expected: u64, }, - #[error("ZLIB block descriptor {index} reports compressed size {compressed_size} and uncompressed size {uncompressed_size}.")] + #[error( + "ZLIB block descriptor {index} reports compressed size {compressed_size} and uncompressed size {uncompressed_size}." + )] ZlibExpansion { index: usize, compressed_size: u32, uncompressed_size: u32, }, - #[error("ZLIB trailer is at offset {zheader:#x} but {descriptors:#x} would be expected from block descriptors.")] + #[error( + "ZLIB trailer is at offset {zheader:#x} but {descriptors:#x} would be expected from block descriptors." + )] ZlibTrailerOffsetInconsistency { descriptors: u64, zheader: u64 }, #[error("File metadata says it contains {expected} cases, but {actual} cases were read.")] @@ -204,7 +236,9 @@ pub enum Warning { #[error("Unexpected end of data inside extension record.")] UnexpectedEndOfData, - #[error("At offset {offset:#x}, at least one valid variable index for value labels is required but none were specified.")] + #[error( + "At offset {offset:#x}, at least one valid variable index for value labels is required but none were specified." + )] NoVarIndexes { offset: u64 }, #[error("At offset {offset:#x}, the first variable index is for a {var_type} variable but the following variable indexes are for {} variables: {wrong_types:?}", !var_type)] @@ -214,14 +248,18 @@ pub enum Warning { wrong_types: Vec, }, - #[error("At offset {offset:#x}, one or more variable indexes for value labels were not in the valid range [1,{max}] or referred to string continuations: {invalid:?}")] + #[error( + "At offset {offset:#x}, one or more variable indexes for value labels were not in the valid range [1,{max}] or referred to string continuations: {invalid:?}" + )] InvalidVarIndexes { offset: u64, max: usize, invalid: Vec, }, - #[error("At offset {offset:#x}, {record} has bad size {size} bytes instead of the expected {expected_size}.")] + #[error( + "At offset {offset:#x}, {record} has bad size {size} bytes instead of the expected {expected_size}." + )] BadRecordSize { offset: u64, record: String, @@ -229,7 +267,9 @@ pub enum Warning { expected_size: u32, }, - #[error("At offset {offset:#x}, {record} has bad count {count} instead of the expected {expected_count}.")] + #[error( + "At offset {offset:#x}, {record} has bad count {count} instead of the expected {expected_count}." + )] BadRecordCount { offset: u64, record: String, @@ -237,14 +277,18 @@ pub enum Warning { expected_count: u32, }, - #[error("In long string missing values record starting at offset {record_offset:#x}, value length at offset {offset:#x} is {value_len} instead of the expected 8.")] + #[error( + "In long string missing values record starting at offset {record_offset:#x}, value length at offset {offset:#x} is {value_len} instead of the expected 8." + )] BadLongMissingValueLength { record_offset: u64, offset: u64, value_len: u32, }, - #[error("The encoding record at offset {offset:#x} contains an encoding name that is not valid UTF-8.")] + #[error( + "The encoding record at offset {offset:#x} contains an encoding name that is not valid UTF-8." + )] BadEncodingName { offset: u64 }, // XXX This is risky because `text` might be arbitarily long. @@ -323,7 +367,9 @@ pub enum Warning { #[error("Syntax error parsing counted string (length {0:?} goes past end of input)")] CountedStringTooLong(usize), - #[error("Variable display record contains {count} items but should contain either {first} or {second}.")] + #[error( + "Variable display record contains {count} items but should contain either {first} or {second}." + )] InvalidVariableDisplayCount { count: usize, first: usize, @@ -357,14 +403,18 @@ pub enum Warning { #[error("Compression bias is {0} instead of the usual values of 0 or 100.")] UnexpectedBias(f64), - #[error("ZLIB block descriptor {index} reported block size {actual:#x}, when {expected:#x} was expected.")] + #[error( + "ZLIB block descriptor {index} reported block size {actual:#x}, when {expected:#x} was expected." + )] ZlibTrailerBlockWrongSize { index: usize, actual: u32, expected: u32, }, - #[error("ZLIB block descriptor {index} reported block size {actual:#x}, when at most {max_expected:#x} was expected.")] + #[error( + "ZLIB block descriptor {index} reported block size {actual:#x}, when at most {max_expected:#x} was expected." + )] ZlibTrailerBlockTooBig { index: usize, actual: u32, @@ -1673,104 +1723,7 @@ fn format_name(type_: u32) -> Cow<'static, str> { .into() } -#[derive(Clone, Default)] -pub struct MissingValues { - /// Individual missing values, up to 3 of them. - values: Vec, - - /// Optional range of missing values. - range: Option, -} - -impl Debug for MissingValues { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - DisplayMissingValues { - mv: self, - encoding: None, - } - .fmt(f) - } -} - -#[derive(Copy, Clone, Debug)] -pub enum MissingValuesError { - TooMany, - TooWide, - MixedTypes, -} - impl MissingValues { - pub fn new( - mut values: Vec, - range: Option, - ) -> Result { - if values.len() > 3 { - return Err(MissingValuesError::TooMany); - } - - let mut var_type = None; - for value in values.iter_mut() { - value.trim_end(); - match value.width() { - VarWidth::String(w) if w > 8 => return Err(MissingValuesError::TooWide), - _ => (), - } - if var_type.is_some_and(|t| t != value.var_type()) { - return Err(MissingValuesError::MixedTypes); - } - var_type = Some(value.var_type()); - } - - if var_type == Some(VarType::String) && range.is_some() { - return Err(MissingValuesError::MixedTypes); - } - - Ok(Self { values, range }) - } - - pub fn is_empty(&self) -> bool { - self.values.is_empty() && self.range.is_none() - } - - pub fn var_type(&self) -> Option { - if let Some(datum) = self.values.first() { - Some(datum.var_type()) - } else if self.range.is_some() { - Some(VarType::Numeric) - } else { - None - } - } - - pub fn contains(&self, value: &Datum) -> bool { - if self - .values - .iter() - .any(|datum| datum.eq_ignore_trailing_spaces(value)) - { - return true; - } - - match value { - Datum::Number(Some(number)) => self.range.is_some_and(|range| range.contains(*number)), - _ => false, - } - } - - pub fn is_resizable(&self, width: VarWidth) -> bool { - self.values.iter().all(|datum| datum.is_resizable(width)) - && self.range.iter().all(|range| range.is_resizable(width)) - } - - pub fn resize(&mut self, width: VarWidth) { - for datum in &mut self.values { - datum.resize(width); - } - if let Some(range) = &mut self.range { - range.resize(width); - } - } - fn read( r: &mut R, offset: u64, @@ -1824,109 +1777,6 @@ impl MissingValues { } Ok(Self::default()) } - - pub fn display(&self, encoding: &'static Encoding) -> DisplayMissingValues<'_> { - DisplayMissingValues { - mv: self, - encoding: Some(encoding), - } - } -} - -pub struct DisplayMissingValues<'a> { - mv: &'a MissingValues, - encoding: Option<&'static Encoding>, -} - -impl<'a> Display for DisplayMissingValues<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - if let Some(range) = &self.mv.range { - write!(f, "{range}")?; - if !self.mv.values.is_empty() { - write!(f, "; ")?; - } - } - - for (i, value) in self.mv.values.iter().enumerate() { - if i > 0 { - write!(f, "; ")?; - } - match self.encoding { - Some(encoding) => value.display_plain(encoding).fmt(f)?, - None => value.fmt(f)?, - } - } - - if self.mv.is_empty() { - write!(f, "none")?; - } - Ok(()) - } -} - -#[derive(Copy, Clone)] -pub enum MissingValueRange { - In { low: f64, high: f64 }, - From { low: f64 }, - To { high: f64 }, -} - -impl MissingValueRange { - pub fn new(low: f64, high: f64) -> Self { - const LOWEST: f64 = f64::MIN.next_up(); - match (low, high) { - (f64::MIN | LOWEST, _) => Self::To { high }, - (_, f64::MAX) => Self::From { low }, - (_, _) => Self::In { low, high }, - } - } - - pub fn low(&self) -> Option { - match self { - MissingValueRange::In { low, .. } | MissingValueRange::From { low } => Some(*low), - MissingValueRange::To { .. } => None, - } - } - - pub fn high(&self) -> Option { - match self { - MissingValueRange::In { high, .. } | MissingValueRange::To { high } => Some(*high), - MissingValueRange::From { .. } => None, - } - } - - pub fn contains(&self, number: f64) -> bool { - match self { - MissingValueRange::In { low, high } => (*low..*high).contains(&number), - MissingValueRange::From { low } => number >= *low, - MissingValueRange::To { high } => number <= *high, - } - } - - pub fn is_resizable(&self, width: VarWidth) -> bool { - width.is_numeric() - } - - pub fn resize(&self, width: VarWidth) { - assert_eq!(width, VarWidth::Numeric); - } -} - -impl Display for MissingValueRange { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match self.low() { - Some(low) => low.display_plain().fmt(f)?, - None => write!(f, "LOW")?, - } - - write!(f, " THRU ")?; - - match self.high() { - Some(high) => high.display_plain().fmt(f)?, - None => write!(f, "HIGH")?, - } - Ok(()) - } } #[derive(Clone)] @@ -1956,19 +1806,30 @@ where pub label: Option, } +/// Width of a variable record. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RawWidth { + /// String continuation. + /// + /// One variable record of this type is present for each 8 bytes after + /// the first 8 bytes of a string variable, as a kind of placeholder. Continuation, + + /// Numeric. Numeric, + + /// String. String(NonZeroU8), } impl RawWidth { + /// Returns the number of value positions corresponding to a variable with + /// this type. pub fn n_values(&self) -> Option { match self { RawWidth::Numeric => Some(1), RawWidth::String(width) => Some((width.get() as usize).div_ceil(8)), - _ => None, + RawWidth::Continuation => None, } } } @@ -2046,7 +1907,7 @@ impl VariableRecord { start_offset, code_offset, code: has_variable_label, - }) + }); } }; @@ -2779,7 +2640,7 @@ impl MultipleResponseSet { _ => { return Err(Warning::MultipleResponseSyntaxError( "missing space preceding variable name", - )) + )); } } } @@ -2884,27 +2745,7 @@ fn parse_counted_string(input: &[u8]) -> Result<(RawString, &[u8]), Warning> { Ok((string.into(), rest)) } -/// [Level of measurement](https://en.wikipedia.org/wiki/Level_of_measurement). -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Measure { - /// Nominal values can only be compared for equality. - Nominal, - - /// Ordinal values can be meaningfully ordered. - Ordinal, - - /// Scale values can be meaningfully compared for the degree of difference. - Scale, -} - impl Measure { - pub fn default_for_type(var_type: VarType) -> Option { - match var_type { - VarType::Numeric => None, - VarType::String => Some(Self::Nominal), - } - } - fn try_decode(source: u32) -> Result, Warning> { match source { 0 => Ok(None), @@ -2914,21 +2755,6 @@ impl Measure { _ => Err(Warning::InvalidMeasurement(source)), } } - - pub fn as_str(&self) -> &'static str { - match self { - Measure::Nominal => "Nominal", - Measure::Ordinal => "Ordinal", - Measure::Scale => "Scale", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Alignment { - Left, - Right, - Center, } impl Alignment { @@ -2940,21 +2766,6 @@ impl Alignment { _ => Err(Warning::InvalidAlignment(source)), } } - - pub fn default_for_type(var_type: VarType) -> Self { - match var_type { - VarType::Numeric => Self::Right, - VarType::String => Self::Left, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - Alignment::Left => "Left", - Alignment::Right => "Right", - Alignment::Center => "Center", - } - } } #[derive(Clone, Debug)] -- 2.30.2