X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=rust%2Fsrc%2Fcooked.rs;h=9f6f0101b0ce51535221779b362008edcd311a94;hb=20e2af4ec687ccbdfe47fe30275dd0121ec3ec16;hp=02f5c23d79d079a6e1509959833433821375868c;hpb=9b851026fd94c614fcce417e357076de4845816c;p=pspp diff --git a/rust/src/cooked.rs b/rust/src/cooked.rs index 02f5c23d79..9f6f0101b0 100644 --- a/rust/src/cooked.rs +++ b/rust/src/cooked.rs @@ -1,10 +1,10 @@ use std::{borrow::Cow, cmp::Ordering, collections::HashMap, iter::repeat}; use crate::{ + endian::Endian, format::{Error as FormatError, Spec, UncheckedSpec}, identifier::{Error as IdError, Identifier}, - raw::{self, MissingValues, VarType}, - {endian::Endian, Compression}, + raw::{self, MissingValues, UnencodedStr, VarType}, }; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use encoding_rs::{DecoderResult, Encoding}; @@ -12,6 +12,8 @@ use num::integer::div_ceil; use ordered_float::OrderedFloat; use thiserror::Error as ThisError; +pub use crate::raw::{CategoryLabels, Compression}; + #[derive(ThisError, Debug)] pub enum Error { #[error("Variable record at offset {offset:#x} specifies width {width} not in valid range [-1,255).")] @@ -73,6 +75,63 @@ pub enum Error { )] InvalidLongStringValueLabel(Identifier), + #[error("Invalid multiple response set name. {0}")] + InvalidMrSetName(IdError), + + #[error("Multiple response set {mr_set} includes unknown variable {short_name}.")] + UnknownMrSetVariable { + mr_set: Identifier, + short_name: Identifier, + }, + + #[error("Multiple response set {0} has no variables.")] + EmptyMrSet(Identifier), + + #[error("Multiple response set {0} has only one variable.")] + OneVarMrSet(Identifier), + + #[error("Multiple response set {0} contains both string and numeric variables.")] + MixedMrSet(Identifier), + + #[error( + "Invalid numeric format for counted value {number} in multiple response set {mr_set}." + )] + InvalidMDGroupCountedValue { mr_set: Identifier, number: String }, + + #[error("Counted value {value} has width {width}, but it must be no wider than {max_width}, the width of the narrowest variable in multiple response set {mr_set}.")] + TooWideMDGroupCountedValue { + mr_set: Identifier, + value: String, + width: usize, + max_width: u16, + }, + + #[error("Long string value label for variable {name} has width {width}, which is not in the valid range [{min_width},{max_width}].")] + InvalidLongValueLabelWidth { + name: Identifier, + width: u32, + min_width: u16, + max_width: u16, + }, + + #[error("Invalid attribute name. {0}")] + InvalidAttributeName(IdError), + + #[error("Invalid short name in long variable name record. {0}")] + InvalidShortName(IdError), + + #[error("Invalid name in long variable name record. {0}")] + InvalidLongName(IdError), + + #[error("Invalid variable name in very long string record. {0}")] + InvalidLongStringName(IdError), + + #[error("Invalid variable name in long string value label record. {0}")] + InvalidLongStringValueLabelName(IdError), + + #[error("Invalid variable name in attribute record. {0}")] + InvalidAttributeVariableName(IdError), + #[error("Details TBD")] TBD, } @@ -87,25 +146,26 @@ pub enum Record { FloatInfo(FloatInfoRecord), VariableSets(VariableSetRecord), VarDisplay(VarDisplayRecord), - //MultipleResponse(MultipleResponseRecord), - //LongStringValueLabels(LongStringValueLabelRecord), + MultipleResponse(MultipleResponseRecord), + LongStringValueLabels(LongStringValueLabelRecord), Encoding(EncodingRecord), NumberOfCases(NumberOfCasesRecord), ProductInfo(ProductInfoRecord), - //LongNames(UnencodedString), - //LongStrings(UnencodedString), - //FileAttributes(UnencodedString), - //VariableAttributes(UnencodedString), - //OtherExtension(Extension), + LongNames(LongNameRecord), + VeryLongStrings(VeryLongStringRecord), + FileAttributes(FileAttributeRecord), + VariableAttributes(VariableAttributeRecord), + OtherExtension(Extension), //EndOfHeaders(u32), //ZHeader(ZHeader), //ZTrailer(ZTrailer), //Case(Vec), } -pub use crate::raw::IntegerInfoRecord; -pub use crate::raw::FloatInfoRecord; pub use crate::raw::EncodingRecord; +pub use crate::raw::Extension; +pub use crate::raw::FloatInfoRecord; +pub use crate::raw::IntegerInfoRecord; pub use crate::raw::NumberOfCasesRecord; type DictIndex = usize; @@ -127,6 +187,26 @@ pub struct Decoder { n_generated_names: usize, } +pub fn decode(headers: Vec) -> Vec { + let encoding = headers.iter().find_map(|rec| { + if let raw::Record::Encoding(ref e) = rec { + Some(e.0.as_str()) + } else { + None + } + }); + let character_code = headers.iter().find_map(|rec| { + if let raw::Record::IntegerInfo(ref r) = rec { + Some(r.character_code) + } else { + None + } + }); + + + Vec::new() +} + impl Decoder { fn generate_name(&mut self) -> Identifier { loop { @@ -139,13 +219,24 @@ impl Decoder { assert!(self.n_generated_names < usize::MAX); } } - fn decode_string<'a>(&self, input: &'a [u8], warn: &impl Fn(Error)) -> Cow<'a, str> { + fn decode_string_cow<'a>(&self, input: &'a [u8], warn: &impl Fn(Error)) -> Cow<'a, str> { let (output, malformed) = self.encoding.decode_without_bom_handling(input); if malformed { warn(Error::TBD); } output } + fn decode_string(&self, input: &[u8], warn: &impl Fn(Error)) -> String { + self.decode_string_cow(input, warn).into() + } + pub fn decode_identifier( + &self, + input: &[u8], + warn: &impl Fn(Error), + ) -> Result { + let s = self.decode_string_cow(input, warn); + Identifier::new(&s, self.encoding) + } fn get_var_by_index(&self, dict_index: usize) -> Result<&Variable, Error> { let max_index = self.n_dict_indexes - 1; if dict_index == 0 || dict_index as usize > max_index { @@ -195,9 +286,23 @@ impl Decoder { } } -pub trait Decode: Sized { +pub trait TryDecode: Sized { type Input; - fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result; + fn try_decode( + decoder: &Decoder, + input: &Self::Input, + warn: impl Fn(Error), + ) -> Result; +} + +pub trait Decode: Sized { + fn decode(decoder: &Decoder, input: &Input, warn: impl Fn(Error)) -> Self; +} + +impl Decode> for String { + fn decode(decoder: &Decoder, input: &UnencodedStr, warn: impl Fn(Error)) -> Self { + decoder.decode_string(&input.0, &warn) + } } #[derive(Clone, Debug)] @@ -209,20 +314,24 @@ pub struct HeaderRecord { pub file_label: String, } -impl Decode for HeaderRecord { +impl TryDecode for HeaderRecord { type Input = crate::raw::HeaderRecord; - fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result { + fn try_decode( + decoder: &Decoder, + input: &Self::Input, + warn: impl Fn(Error), + ) -> Result { let eye_catcher = decoder.decode_string(&input.eye_catcher.0, &warn); let file_label = decoder.decode_string(&input.file_label.0, &warn); - let creation_date = decoder.decode_string(&input.creation_date.0, &warn); + let creation_date = decoder.decode_string_cow(&input.creation_date.0, &warn); let creation_date = NaiveDate::parse_from_str(&creation_date, "%v").unwrap_or_else(|_| { warn(Error::InvalidCreationDate { creation_date: creation_date.into(), }); Default::default() }); - let creation_time = decoder.decode_string(&input.creation_time.0, &warn); + let creation_time = decoder.decode_string_cow(&input.creation_time.0, &warn); let creation_time = NaiveTime::parse_from_str(&creation_time, "%H:%M:%S").unwrap_or_else(|_| { warn(Error::InvalidCreationTime { @@ -231,11 +340,11 @@ impl Decode for HeaderRecord { Default::default() }); Ok(HeaderRecord { - eye_catcher: eye_catcher.into(), + eye_catcher, weight_index: input.weight_index.map(|n| n as usize), n_cases: input.n_cases.map(|n| n as u64), creation: NaiveDateTime::new(creation_date, creation_time), - file_label: file_label.into(), + file_label, }) } } @@ -257,12 +366,42 @@ impl PartialOrd for VarWidth { } impl VarWidth { + const MAX_STRING: u16 = 32767; + fn n_dict_indexes(self) -> usize { match self { VarWidth::Numeric => 1, VarWidth::String(w) => div_ceil(w as usize, 8), } } + + fn width_predicate( + a: Option, + b: Option, + f: impl Fn(u16, u16) -> u16, + ) -> Option { + match (a, b) { + (Some(VarWidth::Numeric), Some(VarWidth::Numeric)) => Some(VarWidth::Numeric), + (Some(VarWidth::String(a)), Some(VarWidth::String(b))) => { + Some(VarWidth::String(f(a, b))) + } + _ => None, + } + } + + /// Returns the wider of `self` and `other`: + /// - Numerical variable widths are equally wide. + /// - Longer strings are wider than shorter strings. + /// - Numerical and string types are incomparable, so result in `None`. + /// - Any `None` in the input yields `None` in the output. + pub fn wider(a: Option, b: Option) -> Option { + Self::width_predicate(a, b, |a, b| a.max(b)) + } + + /// Returns the narrower of `self` and `other` (see [`Self::wider`]). + pub fn narrower(a: Option, b: Option) -> Option { + Self::width_predicate(a, b, |a, b| a.min(b)) + } } impl From for VarType { @@ -312,8 +451,7 @@ impl VariableRecord { }) } }; - let name = decoder.decode_string(&input.name.0, &warn); - let name = match Identifier::new(&name, decoder.encoding) { + let name = match decoder.decode_identifier(&input.name.0, &warn) { Ok(name) => { if !decoder.var_names.contains_key(&name) { name @@ -368,7 +506,7 @@ impl VariableRecord { let label = input .label .as_ref() - .map(|label| decoder.decode_string(&label.0, &warn).into()); + .map(|label| decoder.decode_string(&label.0, &warn)); Ok(Some(VariableRecord { width, name, @@ -383,15 +521,19 @@ impl VariableRecord { #[derive(Clone, Debug)] pub struct DocumentRecord(Vec); -impl Decode for DocumentRecord { +impl TryDecode for DocumentRecord { type Input = crate::raw::DocumentRecord; - fn decode(decoder: &Decoder, input: &Self::Input, warn: impl Fn(Error)) -> Result { + fn try_decode( + decoder: &Decoder, + input: &Self::Input, + warn: impl Fn(Error), + ) -> Result { Ok(DocumentRecord( input .lines .iter() - .map(|s| decoder.decode_string(&s.0, &warn).into()) + .map(|s| decoder.decode_string(&s.0, &warn)) .collect(), )) } @@ -422,6 +564,21 @@ impl VariableSet { } } +trait WarnOnError { + fn warn_on_error(self, warn: &F) -> Option; +} +impl WarnOnError for Result { + fn warn_on_error(self, warn: &F) -> Option { + match self { + Ok(result) => Some(result), + Err(error) => { + warn(error); + None + } + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Value { Number(Option>), @@ -437,28 +594,19 @@ impl Value { } } +#[derive(Clone, Debug)] +pub struct ValueLabel { + pub value: Value, + pub label: String, +} + #[derive(Clone, Debug)] pub struct ValueLabelRecord { pub var_type: VarType, - pub labels: Vec<(Value, String)>, + pub labels: Vec, pub variables: Vec, } -trait WarnOnError { - fn warn_on_error(self, warn: &F) -> Option; -} -impl WarnOnError for Result { - fn warn_on_error(self, warn: &F) -> Option { - match self { - Ok(result) => Some(result), - Err(error) => { - warn(error); - None - } - } - } -} - impl ValueLabelRecord { pub fn decode( decoder: &mut Decoder, @@ -508,8 +656,11 @@ impl ValueLabelRecord { .iter() .map(|(value, label)| { let label = decoder.decode_string(&label.0, &warn); - let value = Value::decode(raw::Value::from_raw(*value, var_type, decoder.endian), &decoder); - (value, label.into()) + let value = Value::decode( + raw::Value::from_raw(*value, var_type, decoder.endian), + &decoder, + ); + ValueLabel { value, label } }) .collect(); let variables = variables @@ -550,43 +701,63 @@ impl TextRecord for ProductInfoRecord { } } -pub struct LongVariableName { - pub short_name: String, - pub long_name: String, +#[derive(Clone, Debug)] +pub struct LongName { + pub short_name: Identifier, + pub long_name: Identifier, } -pub struct LongVariableNameRecord(Vec); +impl LongName { + fn new(decoder: &mut Decoder, short_name: &str, long_name: &str) -> Result { + let short_name = Identifier::new(short_name, decoder.encoding) + .map_err(|e| Error::InvalidShortName(e))?; + let long_name = + Identifier::new(long_name, decoder.encoding).map_err(|e| Error::InvalidLongName(e))?; + Ok(LongName { + short_name, + long_name, + }) + } +} -impl TextRecord for LongVariableNameRecord { - const NAME: &'static str = "long variable names"; - fn parse(input: &str, warn: impl Fn(Error)) -> Result { +#[derive(Clone, Debug)] +pub struct LongNameRecord(Vec); + +impl LongNameRecord { + pub fn parse(decoder: &mut Decoder, input: &str, warn: impl Fn(Error)) -> Result { let mut names = Vec::new(); for pair in input.split('\t').filter(|s| !s.is_empty()) { if let Some((short_name, long_name)) = pair.split_once('=') { - let name = LongVariableName { - short_name: short_name.into(), - long_name: long_name.into(), - }; - names.push(name); + if let Some(long_name) = + LongName::new(decoder, short_name, long_name).warn_on_error(&warn) + { + names.push(long_name); + } } else { warn(Error::TBD) } } - Ok(LongVariableNameRecord(names)) + Ok(LongNameRecord(names)) } } +#[derive(Clone, Debug)] pub struct VeryLongString { - pub short_name: String, - pub length: usize, + pub short_name: Identifier, + pub length: u16, } impl VeryLongString { - fn parse(input: &str) -> Result { + fn parse(decoder: &Decoder, input: &str) -> Result { let Some((short_name, length)) = input.split_once('=') else { return Err(Error::TBD); }; - let length: usize = length.parse().map_err(|_| Error::TBD)?; + let short_name = Identifier::new(short_name, decoder.encoding) + .map_err(|e| Error::InvalidLongStringName(e))?; + let length: u16 = length.parse().map_err(|_| Error::TBD)?; + if length > VarWidth::MAX_STRING { + return Err(Error::TBD); + } Ok(VeryLongString { short_name: short_name.into(), length, @@ -594,18 +765,18 @@ impl VeryLongString { } } +#[derive(Clone, Debug)] pub struct VeryLongStringRecord(Vec); -impl TextRecord for VeryLongStringRecord { - const NAME: &'static str = "very long strings"; - fn parse(input: &str, warn: impl Fn(Error)) -> Result { +impl VeryLongStringRecord { + pub fn parse(decoder: &Decoder, input: &str, warn: impl Fn(Error)) -> Result { let mut very_long_strings = Vec::new(); for tuple in input .split('\0') .map(|s| s.trim_end_matches('\t')) .filter(|s| !s.is_empty()) { - if let Some(vls) = VeryLongString::parse(tuple).warn_on_error(&warn) { + if let Some(vls) = VeryLongString::parse(decoder, tuple).warn_on_error(&warn) { very_long_strings.push(vls) } } @@ -613,13 +784,18 @@ impl TextRecord for VeryLongStringRecord { } } +#[derive(Clone, Debug)] pub struct Attribute { - pub name: String, + pub name: Identifier, pub values: Vec, } impl Attribute { - fn parse<'a>(input: &'a str, warn: &impl Fn(Error)) -> Result<(Attribute, &'a str), Error> { + fn parse<'a>( + decoder: &Decoder, + input: &'a str, + warn: &impl Fn(Error), + ) -> Result<(Option, &'a str), Error> { let Some((name, mut input)) = input.split_once('(') else { return Err(Error::TBD); }; @@ -638,23 +814,23 @@ impl Attribute { values.push(value.into()); } if let Some(rest) = rest.strip_prefix(')') { - return Ok(( - Attribute { - name: name.into(), - values, - }, - rest, - )); - } + let attribute = Identifier::new(name, decoder.encoding) + .map_err(|e| Error::InvalidAttributeName(e)) + .warn_on_error(warn) + .map(|name| Attribute { name, values }); + return Ok((attribute, rest)); + }; input = rest; } } } +#[derive(Clone, Debug)] pub struct AttributeSet(pub Vec); impl AttributeSet { fn parse<'a>( + decoder: &Decoder, mut input: &'a str, sentinel: Option, warn: &impl Fn(Error), @@ -665,8 +841,10 @@ impl AttributeSet { None => break input, c if c == sentinel => break &input[1..], _ => { - let (attribute, rest) = Attribute::parse(input, &warn)?; - attributes.push(attribute); + let (attribute, rest) = Attribute::parse(decoder, input, &warn)?; + if let Some(attribute) = attribute { + attributes.push(attribute); + } input = rest; } } @@ -675,12 +853,12 @@ impl AttributeSet { } } +#[derive(Clone, Debug)] pub struct FileAttributeRecord(AttributeSet); -impl TextRecord for FileAttributeRecord { - const NAME: &'static str = "data file attributes"; - fn parse(input: &str, warn: impl Fn(Error)) -> Result { - let (set, rest) = AttributeSet::parse(input, None, &warn)?; +impl FileAttributeRecord { + pub fn parse(decoder: &Decoder, input: &str, warn: impl Fn(Error)) -> Result { + let (set, rest) = AttributeSet::parse(decoder, input, None, &warn)?; if !rest.is_empty() { warn(Error::TBD); } @@ -688,43 +866,48 @@ impl TextRecord for FileAttributeRecord { } } +#[derive(Clone, Debug)] pub struct VarAttributeSet { - pub long_var_name: String, + pub long_var_name: Identifier, pub attributes: AttributeSet, } impl VarAttributeSet { fn parse<'a>( + decoder: &Decoder, input: &'a str, warn: &impl Fn(Error), - ) -> Result<(VarAttributeSet, &'a str), Error> { + ) -> Result<(Option, &'a str), Error> { let Some((long_var_name, rest)) = input.split_once(':') else { return Err(Error::TBD); }; - let (attributes, rest) = AttributeSet::parse(rest, Some('/'), warn)?; - Ok(( - VarAttributeSet { - long_var_name: long_var_name.into(), + let (attributes, rest) = AttributeSet::parse(decoder, rest, Some('/'), warn)?; + let var_attribute = Identifier::new(long_var_name, decoder.encoding) + .map_err(|e| Error::InvalidAttributeVariableName(e)) + .warn_on_error(warn) + .map(|name| VarAttributeSet { + long_var_name: name, attributes, - }, - rest, - )) + }); + Ok((var_attribute, rest)) } } +#[derive(Clone, Debug)] pub struct VariableAttributeRecord(Vec); -impl TextRecord for VariableAttributeRecord { - const NAME: &'static str = "variable attributes"; - fn parse(mut input: &str, warn: impl Fn(Error)) -> Result { +impl VariableAttributeRecord { + pub fn parse(decoder: &Decoder, mut input: &str, warn: impl Fn(Error)) -> Result { let mut var_attribute_sets = Vec::new(); while !input.is_empty() { let Some((var_attribute, rest)) = - VarAttributeSet::parse(input, &warn).warn_on_error(&warn) + VarAttributeSet::parse(decoder, input, &warn).warn_on_error(&warn) else { break; }; - var_attribute_sets.push(var_attribute); + if let Some(var_attribute) = var_attribute { + var_attribute_sets.push(var_attribute); + } input = rest; } Ok(VariableAttributeRecord(var_attribute_sets)) @@ -755,6 +938,219 @@ pub struct VarDisplay { #[derive(Clone, Debug)] pub struct VarDisplayRecord(pub Vec); +#[derive(Clone, Debug)] +pub enum MultipleResponseType { + MultipleDichotomy { + value: Value, + labels: CategoryLabels, + }, + MultipleCategory, +} + +impl MultipleResponseType { + fn decode( + decoder: &Decoder, + mr_set: &Identifier, + input: &raw::MultipleResponseType, + min_width: VarWidth, + warn: &impl Fn(Error), + ) -> Result { + let mr_type = match input { + raw::MultipleResponseType::MultipleDichotomy { value, labels } => { + let value = decoder.decode_string_cow(&value.0, warn); + let value = match min_width { + VarWidth::Numeric => { + let number: f64 = value.trim().parse().map_err(|_| { + Error::InvalidMDGroupCountedValue { + mr_set: mr_set.clone(), + number: value.into(), + } + })?; + Value::Number(Some(number.into())) + } + VarWidth::String(max_width) => { + let value = value.trim_end_matches(' '); + let width = value.len(); + if width > max_width as usize { + return Err(Error::TooWideMDGroupCountedValue { + mr_set: mr_set.clone(), + value: value.into(), + width, + max_width, + }); + }; + Value::String(value.into()) + } + }; + MultipleResponseType::MultipleDichotomy { + value, + labels: *labels, + } + } + raw::MultipleResponseType::MultipleCategory => MultipleResponseType::MultipleCategory, + }; + Ok(mr_type) + } +} + +#[derive(Clone, Debug)] +pub struct MultipleResponseSet { + pub name: Identifier, + pub min_width: VarWidth, + pub max_width: VarWidth, + pub label: String, + pub mr_type: MultipleResponseType, + pub dict_indexes: Vec, +} + +impl MultipleResponseSet { + fn decode( + decoder: &Decoder, + input: &raw::MultipleResponseSet, + warn: &impl Fn(Error), + ) -> Result { + let mr_set_name = decoder + .decode_identifier(&input.name.0, warn) + .map_err(|error| Error::InvalidMrSetName(error))?; + + let label = decoder.decode_string(&input.label.0, warn); + + let mut dict_indexes = Vec::with_capacity(input.short_names.len()); + for short_name in input.short_names.iter() { + let short_name = match decoder.decode_identifier(&short_name.0, warn) { + Ok(name) => name, + Err(error) => { + warn(Error::InvalidMrSetName(error)); + continue; + } + }; + let Some(&dict_index) = decoder.var_names.get(&short_name) else { + warn(Error::UnknownMrSetVariable { + mr_set: mr_set_name.clone(), + short_name: short_name.clone(), + }); + continue; + }; + dict_indexes.push(dict_index); + } + + match dict_indexes.len() { + 0 => return Err(Error::EmptyMrSet(mr_set_name)), + 1 => return Err(Error::OneVarMrSet(mr_set_name)), + _ => (), + } + + let Some((Some(min_width), Some(max_width))) = dict_indexes + .iter() + .map(|dict_index| decoder.variables[dict_index].width) + .map(|w| (Some(w), Some(w))) + .reduce(|(na, wa), (nb, wb)| (VarWidth::narrower(na, nb), VarWidth::wider(wa, wb))) + else { + return Err(Error::MixedMrSet(mr_set_name)); + }; + + let mr_type = + MultipleResponseType::decode(decoder, &mr_set_name, &input.mr_type, min_width, warn)?; + + Ok(MultipleResponseSet { + name: mr_set_name, + min_width, + max_width, + label, + mr_type, + dict_indexes, + }) + } +} + +#[derive(Clone, Debug)] +pub struct MultipleResponseRecord(pub Vec); + +impl TryDecode for MultipleResponseRecord { + type Input = raw::MultipleResponseRecord; + + fn try_decode( + decoder: &Decoder, + input: &Self::Input, + warn: impl Fn(Error), + ) -> Result { + let mut sets = Vec::with_capacity(input.0.len()); + for set in &input.0 { + match MultipleResponseSet::decode(decoder, set, &warn) { + Ok(set) => sets.push(set), + Err(error) => warn(error), + } + } + Ok(MultipleResponseRecord(sets)) + } +} + +#[derive(Clone, Debug)] +pub struct LongStringValueLabels { + pub var_name: Identifier, + pub width: VarWidth, + pub labels: Vec, +} + +impl LongStringValueLabels { + fn decode( + decoder: &Decoder, + input: &raw::LongStringValueLabels, + warn: &impl Fn(Error), + ) -> Result { + let var_name = decoder + .decode_identifier(&input.var_name.0, warn) + .map_err(|e| Error::InvalidLongStringValueLabelName(e))?; + + let min_width = 9; + let max_width = VarWidth::MAX_STRING; + if input.width < 9 || input.width > max_width as u32 { + return Err(Error::InvalidLongValueLabelWidth { + name: var_name.into(), + width: input.width, + min_width, + max_width, + }); + } + let width = input.width as u16; + + let mut labels = Vec::with_capacity(input.labels.len()); + for (value, label) in input.labels.iter() { + let value = Value::String(decoder.decode_exact_length(&value.0).into()); + let label = decoder.decode_string(&label.0, warn); + labels.push(ValueLabel { value, label }); + } + + Ok(LongStringValueLabels { + var_name, + width: VarWidth::String(width), + labels, + }) + } +} + +#[derive(Clone, Debug)] +pub struct LongStringValueLabelRecord(pub Vec); + +impl TryDecode for LongStringValueLabelRecord { + type Input = raw::LongStringValueLabelRecord; + + fn try_decode( + decoder: &Decoder, + input: &Self::Input, + warn: impl Fn(Error), + ) -> Result { + let mut labels = Vec::with_capacity(input.0.len()); + for label in &input.0 { + match LongStringValueLabels::decode(decoder, label, &warn) { + Ok(set) => labels.push(set), + Err(error) => warn(error), + } + } + Ok(LongStringValueLabelRecord(labels)) + } +} + #[cfg(test)] mod test { use encoding_rs::WINDOWS_1252;