X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=rust%2Fsrc%2Fcooked.rs;h=9f6f0101b0ce51535221779b362008edcd311a94;hb=20e2af4ec687ccbdfe47fe30275dd0121ec3ec16;hp=8d748778b99797151813894b98d2d258b9f3dce2;hpb=240a640e30cefb5b69918bbee600475655c2bcb7;p=pspp diff --git a/rust/src/cooked.rs b/rust/src/cooked.rs index 8d748778b9..9f6f0101b0 100644 --- a/rust/src/cooked.rs +++ b/rust/src/cooked.rs @@ -1,11 +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}, - CategoryLabels, - {endian::Endian, Compression}, + raw::{self, MissingValues, UnencodedStr, VarType}, }; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; use encoding_rs::{DecoderResult, Encoding}; @@ -13,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).")] @@ -75,7 +76,7 @@ pub enum Error { InvalidLongStringValueLabel(Identifier), #[error("Invalid multiple response set name. {0}")] - InvalidMrSetName(#[from] IdError), + InvalidMrSetName(IdError), #[error("Multiple response set {mr_set} includes unknown variable {short_name}.")] UnknownMrSetVariable { @@ -92,6 +93,45 @@ pub enum Error { #[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, } @@ -107,15 +147,15 @@ pub enum Record { VariableSets(VariableSetRecord), VarDisplay(VarDisplayRecord), MultipleResponse(MultipleResponseRecord), - //LongStringValueLabels(LongStringValueLabelRecord), + 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), @@ -123,6 +163,7 @@ pub enum Record { } 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; @@ -146,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 { @@ -158,19 +219,22 @@ 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(input, warn); + 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> { @@ -222,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)] @@ -236,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 { @@ -258,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, }) } } @@ -284,6 +366,8 @@ impl PartialOrd for VarWidth { } impl VarWidth { + const MAX_STRING: u16 = 32767; + fn n_dict_indexes(self) -> usize { match self { VarWidth::Numeric => 1, @@ -291,20 +375,33 @@ impl VarWidth { } } - /// 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 { + 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(a.max(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 { @@ -409,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, @@ -424,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(), )) } @@ -463,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>), @@ -478,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, @@ -553,7 +660,7 @@ impl ValueLabelRecord { raw::Value::from_raw(*value, var_type, decoder.endian), &decoder, ); - (value, label.into()) + ValueLabel { value, label } }) .collect(); let variables = variables @@ -594,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, @@ -638,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) } } @@ -657,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); }; @@ -682,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), @@ -709,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; } } @@ -719,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); } @@ -732,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)) @@ -808,9 +947,57 @@ pub enum MultipleResponseType { 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, @@ -821,15 +1008,15 @@ impl MultipleResponseSet { decoder: &Decoder, input: &raw::MultipleResponseSet, warn: &impl Fn(Error), - ) -> Result, 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).into(); + let label = decoder.decode_string(&input.label.0, warn); - let dict_indexes = Vec::with_capacity(input.short_names.len()); - for &short_name in input.short_names.iter() { + 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) => { @@ -837,7 +1024,7 @@ impl MultipleResponseSet { continue; } }; - let Some(dict_index) = decoder.var_names.get(&short_name) else { + 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(), @@ -853,24 +1040,114 @@ impl MultipleResponseSet { _ => (), } - let Some(var_width) = dict_indexes + let Some((Some(min_width), Some(max_width))) = dict_indexes .iter() - .map(|&dict_index| Some(decoder.variables[dict_index].width)) - .reduce(|a, b| VarWidth::wider(a, b)) - .flatten() + .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(Vec); +pub struct MultipleResponseRecord(pub Vec); -impl Decode for MultipleResponseRecord { +impl TryDecode for MultipleResponseRecord { type Input = raw::MultipleResponseRecord; - 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 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)) } }