Skip to main content

pspp/
variable.rs

1// PSPP - a program for statistical analysis.
2// Copyright (C) 2025 Free Software Foundation, Inc.
3//
4// This program is free software: you can redistribute it and/or modify it under
5// the terms of the GNU General Public License as published by the Free Software
6// Foundation, either version 3 of the License, or (at your option) any later
7// version.
8//
9// This program is distributed in the hope that it will be useful, but WITHOUT
10// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
12// details.
13//
14// You should have received a copy of the GNU General Public License along with
15// this program.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Variables.
18
19use std::{
20    collections::BTreeMap,
21    fmt::{Debug, Display},
22    hash::{DefaultHasher, Hash, Hasher},
23    ops::{Deref, Not},
24    str::FromStr,
25};
26
27use displaydoc::Display;
28use encoding_rs::{Encoding, UTF_8};
29use hashbrown::HashMap;
30use indexmap::Equivalent;
31use num::integer::div_ceil;
32use serde::{Serialize, ser::SerializeSeq};
33use thiserror::Error as ThisError;
34use unicase::UniCase;
35
36use crate::{
37    data::{
38        ByteStr, ByteString, Datum, Encoded, EncodedString, RawString, ResizeError, WithEncoding,
39    },
40    format::{DisplayPlain, Format},
41    identifier::{HasIdentifier, Identifier},
42};
43
44/// Variable type.
45#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
46pub enum VarType {
47    /// A numeric variable.
48    Numeric,
49
50    /// A string variable.
51    ///
52    /// The string width is unspecified; use [VarWidth] for type and width
53    /// together.
54    String,
55}
56
57impl VarType {
58    pub fn is_numeric(&self) -> bool {
59        *self == Self::Numeric
60    }
61
62    pub fn is_string(&self) -> bool {
63        *self == Self::String
64    }
65}
66
67impl Not for VarType {
68    type Output = Self;
69
70    fn not(self) -> Self::Output {
71        match self {
72            Self::Numeric => Self::String,
73            Self::String => Self::Numeric,
74        }
75    }
76}
77
78impl Not for &VarType {
79    type Output = VarType;
80
81    fn not(self) -> Self::Output {
82        !*self
83    }
84}
85
86impl Display for VarType {
87    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
88        match self {
89            VarType::Numeric => write!(f, "numeric"),
90            VarType::String => write!(f, "string"),
91        }
92    }
93}
94
95/// A variable's width.
96///
97/// This is essentially [VarType] plus a width for [VarType::String].
98#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
99pub enum VarWidth {
100    /// A numeric variable.
101    Numeric,
102
103    /// A string variable.
104    String(
105        /// The width of the string variable.
106        ///
107        /// Must be in `1..=32767`, although the type system does not yet
108        /// enforce this.
109        u16,
110    ), // XXX change to NonZeroU16, or to 1..=32767 range type
111}
112
113impl VarWidth {
114    pub const MAX_STRING: u16 = 32767;
115
116    pub fn n_dict_indexes(self) -> usize {
117        match self {
118            VarWidth::Numeric => 1,
119            VarWidth::String(w) => div_ceil(w as usize, 8),
120        }
121    }
122
123    fn width_predicate(a: VarWidth, b: VarWidth, f: impl Fn(u16, u16) -> u16) -> Option<VarWidth> {
124        match (a, b) {
125            (VarWidth::Numeric, VarWidth::Numeric) => Some(VarWidth::Numeric),
126            (VarWidth::String(a), VarWidth::String(b)) => Some(VarWidth::String(f(a, b))),
127            _ => None,
128        }
129    }
130
131    /// Returns the wider of `self` and `other`:
132    /// - Numerical variable widths are equally wide.
133    /// - Longer strings are wider than shorter strings.
134    /// - Numerical and string types are incomparable, so result in `None`.
135    pub fn wider(a: VarWidth, b: VarWidth) -> Option<VarWidth> {
136        Self::width_predicate(a, b, |a, b| a.max(b))
137    }
138
139    /// Returns the narrower of `self` and `other` (see [`Self::wider`]).
140    pub fn narrower(a: VarWidth, b: VarWidth) -> Option<VarWidth> {
141        Self::width_predicate(a, b, |a, b| a.min(b))
142    }
143
144    pub fn default_display_width(&self) -> u32 {
145        match self {
146            VarWidth::Numeric => 8,
147            VarWidth::String(width) => *width.min(&32) as u32,
148        }
149    }
150
151    pub fn is_long_string(&self) -> bool {
152        if let Self::String(width) = self {
153            *width > 8
154        } else {
155            false
156        }
157    }
158
159    pub fn as_string_width(&self) -> Option<usize> {
160        match self {
161            VarWidth::Numeric => None,
162            VarWidth::String(width) => Some(*width as usize),
163        }
164    }
165
166    pub fn is_numeric(&self) -> bool {
167        *self == Self::Numeric
168    }
169
170    pub fn is_string(&self) -> bool {
171        !self.is_numeric()
172    }
173
174    /// Returns true if this is a very long string width, meaning wider than 255
175    /// bytes, which was the limit for old versions of SPSS.
176    pub fn is_very_long_string(&self) -> bool {
177        match *self {
178            VarWidth::Numeric => false,
179            VarWidth::String(width) => width > 255,
180        }
181    }
182
183    /// Number of bytes per segment by which the amount of space for very long
184    /// string variables is allocated.
185    pub const SEGMENT_SIZE: usize = 252;
186
187    /// Returns an iterator over the "segments" used for writing case data for a
188    /// variable with this width.  A segment is a physical variable in the
189    /// system file that represents some piece of a logical variable as seen by
190    /// a PSPP user.  Most variables have one segment whose width is their own
191    /// width, but very long string variables, with width greater than 255, have
192    /// multiple segments each with width 255 or less.
193    pub fn segments(&self) -> Segments {
194        Segments::new(*self)
195    }
196
197    /// Returns the number of 8-byte chunks used for writing case data for a
198    /// variable with this width.  Very long string variables (wider than 255
199    /// bytes) cannot directly be divided into chunks: they must first be
200    /// divided into multiple [segments](Self::segments), which can then be
201    /// divided into chunks.
202    pub fn n_chunks(&self) -> Option<usize> {
203        match *self {
204            VarWidth::Numeric => Some(1),
205            VarWidth::String(w) if w <= 255 => Some(w.div_ceil(8) as usize),
206            VarWidth::String(_) => None,
207        }
208    }
209
210    /// Returns the width to allocate to the segment with the given
211    /// `segment_idx` within this variable.  A segment is a physical variable in
212    /// the system file that represents some piece of a logical variable as seen
213    /// by a PSPP user.
214    pub fn segment_alloc_width(&self, segment_idx: usize) -> usize {
215        debug_assert!(segment_idx < self.segments().len());
216        debug_assert!(self.is_very_long_string());
217
218        if segment_idx < self.segments().len() - 1 {
219            255
220        } else {
221            self.as_string_width().unwrap() - segment_idx * Self::SEGMENT_SIZE
222        }
223    }
224
225    pub fn display_adjective(&self) -> VarWidthAdjective {
226        VarWidthAdjective(*self)
227    }
228
229    pub fn codepage_to_unicode(&mut self) {
230        match self {
231            VarWidth::Numeric => (),
232            VarWidth::String(width) => *width = width.saturating_mul(3).min(Self::MAX_STRING),
233        }
234    }
235}
236
237pub struct Segments {
238    width: VarWidth,
239    i: usize,
240    n: usize,
241}
242impl Segments {
243    pub fn new(width: VarWidth) -> Self {
244        Self {
245            width,
246            i: 0,
247            n: if width.is_very_long_string() {
248                width
249                    .as_string_width()
250                    .unwrap()
251                    .div_ceil(VarWidth::SEGMENT_SIZE)
252            } else {
253                1
254            },
255        }
256    }
257}
258
259impl Iterator for Segments {
260    type Item = VarWidth;
261
262    fn next(&mut self) -> Option<Self::Item> {
263        let i = self.i;
264        if i >= self.n {
265            None
266        } else {
267            self.i += 1;
268            match self.width {
269                VarWidth::Numeric => Some(VarWidth::Numeric),
270                VarWidth::String(_) if i < self.n - 1 => Some(VarWidth::String(255)),
271                VarWidth::String(width) => Some(VarWidth::String(
272                    width - (self.n as u16 - 1) * VarWidth::SEGMENT_SIZE as u16,
273                )),
274            }
275        }
276    }
277
278    fn size_hint(&self) -> (usize, Option<usize>) {
279        let n = self.n - self.i;
280        (n, Some(n))
281    }
282}
283
284impl ExactSizeIterator for Segments {}
285
286impl From<VarWidth> for VarType {
287    fn from(source: VarWidth) -> Self {
288        match source {
289            VarWidth::Numeric => VarType::Numeric,
290            VarWidth::String(_) => VarType::String,
291        }
292    }
293}
294
295pub struct VarWidthAdjective(VarWidth);
296
297impl Display for VarWidthAdjective {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        match self.0 {
300            VarWidth::Numeric => write!(f, "numeric"),
301            VarWidth::String(width) => write!(f, "{width}-byte string"),
302        }
303    }
304}
305
306#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
307pub enum Role {
308    #[default]
309    Input,
310    Target,
311    Both,
312    None,
313    Partition,
314    Split,
315}
316
317impl Role {
318    pub fn as_str(&self) -> &'static str {
319        match self {
320            Role::Input => "Input",
321            Role::Target => "Target",
322            Role::Both => "Both",
323            Role::None => "None",
324            Role::Partition => "Partition",
325            Role::Split => "Split",
326        }
327    }
328}
329
330impl FromStr for Role {
331    type Err = InvalidRole;
332
333    fn from_str(s: &str) -> Result<Self, Self::Err> {
334        for (string, value) in [
335            ("input", Role::Input),
336            ("target", Role::Target),
337            ("both", Role::Both),
338            ("none", Role::None),
339            ("partition", Role::Partition),
340            ("split", Role::Split),
341        ] {
342            if string.eq_ignore_ascii_case(s) {
343                return Ok(value);
344            }
345        }
346        Err(InvalidRole::UnknownRole(s.into()))
347    }
348}
349
350impl TryFrom<i32> for Role {
351    type Error = InvalidRole;
352
353    fn try_from(value: i32) -> Result<Self, Self::Error> {
354        match value {
355            0 => Ok(Role::Input),
356            1 => Ok(Role::Target),
357            2 => Ok(Role::Both),
358            3 => Ok(Role::None),
359            4 => Ok(Role::Partition),
360            5 => Ok(Role::Split),
361            _ => Err(InvalidRole::UnknownRole(value.to_string())),
362        }
363    }
364}
365
366impl From<Role> for i32 {
367    fn from(value: Role) -> Self {
368        match value {
369            Role::Input => 0,
370            Role::Target => 1,
371            Role::Both => 2,
372            Role::None => 3,
373            Role::Partition => 4,
374            Role::Split => 5,
375        }
376    }
377}
378
379#[derive(Clone, Default, PartialEq, Eq, Serialize)]
380pub struct Attributes(pub BTreeMap<Identifier, Vec<String>>);
381
382impl Attributes {
383    pub fn new() -> Self {
384        Self(BTreeMap::new())
385    }
386
387    pub fn contains_name(&self, name: &Identifier) -> bool {
388        self.0.contains_key(name)
389    }
390
391    pub fn insert(&mut self, name: Identifier, values: Vec<String>) {
392        self.0.insert(name, values);
393    }
394
395    pub fn with(mut self, name: Identifier, values: Vec<String>) -> Self {
396        self.insert(name, values);
397        self
398    }
399
400    pub fn append(&mut self, other: &mut Self) {
401        self.0.append(&mut other.0)
402    }
403
404    pub fn role(&self) -> Result<Option<Role>, InvalidRole> {
405        self.try_into()
406    }
407
408    pub fn iter(&self, include_at: bool) -> impl Iterator<Item = (&Identifier, &[String])> {
409        self.0.iter().filter_map(move |(name, values)| {
410            if include_at || !name.0.starts_with('@') {
411                Some((name, values.as_slice()))
412            } else {
413                None
414            }
415        })
416    }
417
418    pub fn has_any(&self, include_at: bool) -> bool {
419        self.iter(include_at).next().is_some()
420    }
421
422    pub fn codepage_to_unicode(&mut self) {
423        let mut new = BTreeMap::new();
424        while let Some((mut name, value)) = self.0.pop_first() {
425            name.codepage_to_unicode();
426            new.insert(name, value);
427        }
428        self.0 = new;
429    }
430}
431
432impl Debug for Attributes {
433    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434        self.0.fmt(f)
435    }
436}
437
438#[derive(Clone, Debug, ThisError, PartialEq, Eq)]
439pub enum InvalidRole {
440    #[error("Unknown role {0:?}.")]
441    UnknownRole(String),
442
443    #[error("Role attribute $@Role must have exactly one value (not {0}).")]
444    InvalidValues(usize),
445}
446
447impl TryFrom<&Attributes> for Option<Role> {
448    type Error = InvalidRole;
449
450    fn try_from(value: &Attributes) -> Result<Self, Self::Error> {
451        let role = Identifier::new("$@Role").unwrap();
452        value.0.get(&role).map_or(Ok(None), |attribute| {
453            if let Ok([string]) = <&[String; 1]>::try_from(attribute.as_slice()) {
454                match string.parse::<i32>() {
455                    Ok(integer) => Ok(Some(Role::try_from(integer)?)),
456                    Err(_) => Err(InvalidRole::UnknownRole(string.clone())),
457                }
458            } else {
459                Err(InvalidRole::InvalidValues(attribute.len()))
460            }
461        })
462    }
463}
464
465/// A variable, usually inside a [Dictionary].
466///
467/// [Dictionary]: crate::dictionary::Dictionary
468#[derive(Clone, Debug, Serialize)]
469pub struct Variable {
470    /// The variable's name.
471    ///
472    /// PSPP variable names are case-insensitive.
473    pub name: Identifier,
474
475    /// Variable width.
476    pub width: VarWidth,
477
478    /// User-missing values.
479    ///
480    /// Numeric variables also have a system-missing value (represented as
481    /// `None`).
482    ///
483    /// Both kinds of missing values are excluded from most analyses.
484    missing_values: MissingValues,
485
486    /// Output format used in most contexts.
487    pub print_format: Format,
488
489    /// Output format used on the `WRITE` command.
490    pub write_format: Format,
491
492    /// Value labels.
493    pub value_labels: ValueLabels,
494
495    /// Variable label, an optional meaningful description for the variable
496    /// itself.
497    pub label: Option<String>,
498
499    /// Measurement level for the variable's data.
500    pub measure: Option<Measure>,
501
502    /// Role in data analysis.
503    pub role: Role,
504
505    /// Width of data column in GUI.
506    pub display_width: u32,
507
508    /// Data alignment in GUI.
509    pub alignment: Alignment,
510
511    /// Whether to retain values of the variable from one case to the next.
512    pub leave: bool,
513
514    /// For compatibility with old software that supported at most 8-character
515    /// variable names.
516    pub short_names: Vec<Identifier>,
517
518    /// Variable attributes.
519    pub attributes: Attributes,
520
521    /// Encoding for [Value]s inside this variable.
522    ///
523    /// The variables in a [Dictionary] must all use the same encoding as the
524    /// dictionary.
525    encoding: &'static Encoding,
526}
527
528impl Variable {
529    pub fn new(name: Identifier, width: VarWidth, encoding: &'static Encoding) -> Self {
530        let var_type = VarType::from(width);
531        let leave = name.class().must_leave();
532        Self {
533            name,
534            width,
535            missing_values: MissingValues::default(),
536            print_format: Format::default_for_width(width),
537            write_format: Format::default_for_width(width),
538            value_labels: ValueLabels::new(),
539            label: None,
540            measure: Measure::default_for_type(var_type),
541            role: Role::default(),
542            display_width: width.default_display_width(),
543            alignment: Alignment::default_for_type(var_type),
544            leave,
545            short_names: Vec::new(),
546            attributes: Attributes::new(),
547            encoding,
548        }
549    }
550
551    pub fn encoding(&self) -> &'static Encoding {
552        self.encoding
553    }
554
555    pub fn is_numeric(&self) -> bool {
556        self.width.is_numeric()
557    }
558
559    pub fn is_string(&self) -> bool {
560        self.width.is_string()
561    }
562
563    pub fn label(&self) -> Option<&String> {
564        self.label.as_ref()
565    }
566
567    pub fn resize(&mut self, width: VarWidth) {
568        let _ = self.missing_values.resize(width);
569
570        self.value_labels.resize(width);
571
572        self.print_format.resize(width);
573        self.write_format.resize(width);
574
575        self.width = width;
576    }
577
578    pub fn missing_values(&self) -> &MissingValues {
579        &self.missing_values
580    }
581
582    pub fn missing_values_mut(&mut self) -> MissingValuesMut<'_> {
583        MissingValuesMut {
584            inner: &mut self.missing_values,
585            width: self.width,
586        }
587    }
588
589    pub fn codepage_to_unicode(&mut self) {
590        self.name.codepage_to_unicode();
591        self.width.codepage_to_unicode();
592        self.missing_values.codepage_to_unicode();
593        self.print_format.codepage_to_unicode();
594        self.write_format.codepage_to_unicode();
595        self.attributes.codepage_to_unicode();
596        self.encoding = UTF_8;
597
598        // Anything old enough to not support long names is old enough not to
599        // support Unicode.
600        self.short_names.clear();
601    }
602}
603
604impl HasIdentifier for Variable {
605    fn identifier(&self) -> &UniCase<String> {
606        &self.name.0
607    }
608}
609
610/// Associates values of a variable with meaningful labels.
611///
612/// For example, 1 => strongly disagree, 2 => disagree, 3 => neither agree nor
613/// disagree, ...
614#[derive(Clone, Default, PartialEq, Eq)]
615pub struct ValueLabels(pub HashMap<Datum<ByteString>, String>);
616
617impl Equivalent<Datum<ByteString>> for Datum<&ByteStr> {
618    fn equivalent(&self, key: &Datum<ByteString>) -> bool {
619        self == key
620    }
621}
622
623impl ValueLabels {
624    pub fn new() -> Self {
625        Self::default()
626    }
627
628    pub fn is_empty(&self) -> bool {
629        self.len() == 0
630    }
631
632    pub fn len(&self) -> usize {
633        self.0.len()
634    }
635
636    pub fn get<T>(&self, value: &Datum<T>) -> Option<&str>
637    where
638        T: RawString,
639    {
640        self.0.get(&value.as_raw()).map(|s| s.as_str())
641    }
642
643    pub fn insert(&mut self, value: Datum<ByteString>, label: impl Into<String>) -> Option<String> {
644        self.0.insert(value, label.into())
645    }
646
647    pub fn is_resizable(&self, width: VarWidth) -> bool {
648        self.0.keys().all(|datum| datum.is_resizable(width))
649    }
650
651    pub fn resize(&mut self, width: VarWidth) {
652        self.0 = self
653            .0
654            .drain()
655            .filter_map(|(mut datum, string)| {
656                datum.resize(width).is_ok().then_some((datum, string))
657            })
658            .collect();
659    }
660
661    pub fn codepage_to_unicode(&mut self, encoding: &'static Encoding) {
662        self.0 = self
663            .0
664            .drain()
665            .map(|(key, value)| {
666                let mut key = key.with_encoding(encoding);
667                key.codepage_to_unicode();
668                (key.without_encoding(), value)
669            })
670            .collect();
671    }
672}
673
674impl Serialize for ValueLabels {
675    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
676    where
677        S: serde::Serializer,
678    {
679        let mut map = serializer.serialize_seq(Some(self.0.len()))?;
680        for tuple in &self.0 {
681            map.serialize_element(&tuple)?;
682        }
683        map.end()
684    }
685}
686
687impl Debug for ValueLabels {
688    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
689        self.0.fmt(f)
690    }
691}
692
693impl Hash for ValueLabels {
694    fn hash<H: Hasher>(&self, state: &mut H) {
695        let mut hash = 0;
696        for (k, v) in &self.0 {
697            let mut hasher = DefaultHasher::new();
698            k.hash(&mut hasher);
699            v.hash(&mut hasher);
700            hash ^= hasher.finish();
701        }
702        state.write_u64(hash);
703    }
704}
705
706impl<'a> IntoIterator for &'a ValueLabels {
707    type Item = (&'a Datum<ByteString>, &'a String);
708
709    type IntoIter = hashbrown::hash_map::Iter<'a, Datum<ByteString>, String>;
710
711    fn into_iter(self) -> Self::IntoIter {
712        self.0.iter()
713    }
714}
715
716pub struct MissingValuesMut<'a> {
717    inner: &'a mut MissingValues,
718    width: VarWidth,
719}
720
721impl<'a> Deref for MissingValuesMut<'a> {
722    type Target = MissingValues;
723
724    fn deref(&self) -> &Self::Target {
725        self.inner
726    }
727}
728
729impl<'a> MissingValuesMut<'a> {
730    pub fn replace(&mut self, mut new: MissingValues) -> Result<(), MissingValuesError> {
731        new.resize(self.width)?;
732        *self.inner = new;
733        Ok(())
734    }
735
736    pub fn add_value(
737        &mut self,
738        mut value: Datum<WithEncoding<ByteString>>,
739    ) -> Result<(), MissingValuesError> {
740        if self.inner.values.len() > 2
741            || (self.inner.range().is_some() && self.inner.values.len() > 1)
742        {
743            Err(MissingValuesError::TooMany)
744        } else if value.var_type() != VarType::from(self.width) {
745            Err(MissingValuesError::MixedTypes)
746        } else if value.is_sysmis() {
747            Err(MissingValuesError::SystemMissing)
748        } else if value.resize(self.width.min(VarWidth::String(8))).is_err() {
749            Err(MissingValuesError::TooWide)
750        } else {
751            value.trim_end();
752            self.inner.values.push(value);
753            Ok(())
754        }
755    }
756
757    pub fn add_values(
758        &mut self,
759        values: impl IntoIterator<Item = Datum<WithEncoding<ByteString>>>,
760    ) -> Result<(), MissingValuesError> {
761        let n = self.inner.values.len();
762        for value in values {
763            self.add_value(value)
764                .inspect_err(|_| self.inner.values.truncate(n))?;
765        }
766        Ok(())
767    }
768
769    pub fn add_range(&mut self, range: MissingValueRange) -> Result<(), MissingValuesError> {
770        if self.inner.range.is_some() || self.inner.values().len() > 1 {
771            Err(MissingValuesError::TooMany)
772        } else if self.width != VarWidth::Numeric {
773            Err(MissingValuesError::MixedTypes)
774        } else {
775            self.inner.range = Some(range);
776            Ok(())
777        }
778    }
779}
780
781// Currently doesn't filter out duplicates (should it?).
782#[derive(Clone, Default, Serialize, PartialEq)]
783pub struct MissingValues {
784    /// Individual missing values, up to 3 of them.
785    values: Vec<Datum<WithEncoding<ByteString>>>,
786
787    /// Optional range of missing values.
788    range: Option<MissingValueRange>,
789}
790
791impl Debug for MissingValues {
792    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
793        write!(f, "{self}")
794    }
795}
796
797impl Display for MissingValues {
798    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
799        if let Some(range) = &self.range {
800            write!(f, "{range}")?;
801            if !self.values.is_empty() {
802                write!(f, "; ")?;
803            }
804        }
805
806        for (i, value) in self.values.iter().enumerate() {
807            if i > 0 {
808                write!(f, "; ")?;
809            }
810            write!(f, "{}", value.quoted())?;
811        }
812
813        if self.is_empty() {
814            write!(f, "none")?;
815        }
816        Ok(())
817    }
818}
819
820/// Invalid missing values.
821#[derive(Display, Copy, Clone, Debug, ThisError)]
822pub enum MissingValuesError {
823    /// Too many missing values.
824    TooMany,
825
826    /// Missing values too wide (missing values may be no wider than 8 bytes).
827    TooWide,
828
829    /// Missing values must be all string or all numeric.
830    MixedTypes,
831
832    /// The system-missing value may not be a user-missing value.
833    SystemMissing,
834}
835
836impl From<ResizeError> for MissingValuesError {
837    fn from(value: ResizeError) -> Self {
838        match value {
839            ResizeError::MixedTypes => MissingValuesError::MixedTypes,
840            ResizeError::TooWide => MissingValuesError::TooWide,
841        }
842    }
843}
844
845impl MissingValues {
846    pub fn clear(&mut self) {
847        *self = Self::default();
848    }
849    pub fn values(&self) -> &[Datum<WithEncoding<ByteString>>] {
850        &self.values
851    }
852
853    pub fn range(&self) -> Option<&MissingValueRange> {
854        self.range.as_ref()
855    }
856
857    pub fn new(
858        mut values: Vec<Datum<WithEncoding<ByteString>>>,
859        range: Option<MissingValueRange>,
860    ) -> Result<Self, MissingValuesError> {
861        if values.len() > 3 {
862            return Err(MissingValuesError::TooMany);
863        }
864
865        let mut var_type = None;
866        for value in values.iter_mut() {
867            value.trim_end();
868            if value.width().is_long_string() {
869                return Err(MissingValuesError::TooWide);
870            }
871            if var_type.is_some_and(|t| t != value.var_type()) {
872                return Err(MissingValuesError::MixedTypes);
873            }
874            var_type = Some(value.var_type());
875        }
876
877        if var_type == Some(VarType::String) && range.is_some() {
878            return Err(MissingValuesError::MixedTypes);
879        }
880
881        Ok(Self { values, range })
882    }
883
884    pub fn is_empty(&self) -> bool {
885        self.values.is_empty() && self.range.is_none()
886    }
887
888    pub fn var_type(&self) -> Option<VarType> {
889        if let Some(datum) = self.values.first() {
890            Some(datum.var_type())
891        } else if self.range.is_some() {
892            Some(VarType::Numeric)
893        } else {
894            None
895        }
896    }
897
898    pub fn contains<S>(&self, value: &Datum<S>) -> bool
899    where
900        S: EncodedString,
901    {
902        if self
903            .values
904            .iter()
905            .any(|datum| datum.eq_ignore_trailing_spaces(value))
906        {
907            return true;
908        }
909
910        if let Some(Some(number)) = value.as_number()
911            && let Some(range) = self.range
912        {
913            range.contains(number)
914        } else {
915            false
916        }
917    }
918
919    pub fn resize(&mut self, width: VarWidth) -> Result<(), MissingValuesError> {
920        fn inner(this: &mut MissingValues, width: VarWidth) -> Result<(), MissingValuesError> {
921            for datum in &mut this.values {
922                datum.resize(width)?;
923                datum.trim_end();
924            }
925            if let Some(range) = &mut this.range {
926                range.resize(width)?;
927            }
928            Ok(())
929        }
930        inner(self, width).inspect_err(|_| self.clear())
931    }
932
933    pub fn codepage_to_unicode(&mut self) {
934        self.values = self
935            .values
936            .drain(..)
937            .map(|value| match value {
938                Datum::Number(number) => Datum::Number(number),
939                Datum::String(s) => Datum::String(if s.encoding() != UTF_8 {
940                    let mut new_s = ByteString::from(s.as_str());
941                    new_s.0.truncate(8);
942                    WithEncoding::new(new_s, UTF_8)
943                } else {
944                    s
945                }),
946            })
947            .collect();
948    }
949}
950
951#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
952pub enum MissingValueRange {
953    In { low: f64, high: f64 },
954    From { low: f64 },
955    To { high: f64 },
956}
957
958impl MissingValueRange {
959    pub fn new(low: f64, high: f64) -> Self {
960        const LOWEST: f64 = f64::MIN.next_up();
961        match (low, high) {
962            (f64::MIN | LOWEST, _) => Self::To { high },
963            (_, f64::MAX) => Self::From { low },
964            (_, _) => Self::In { low, high },
965        }
966    }
967
968    pub fn low(&self) -> Option<f64> {
969        match self {
970            MissingValueRange::In { low, .. } | MissingValueRange::From { low } => Some(*low),
971            MissingValueRange::To { .. } => None,
972        }
973    }
974
975    pub fn high(&self) -> Option<f64> {
976        match self {
977            MissingValueRange::In { high, .. } | MissingValueRange::To { high } => Some(*high),
978            MissingValueRange::From { .. } => None,
979        }
980    }
981
982    pub fn contains(&self, number: f64) -> bool {
983        match self {
984            MissingValueRange::In { low, high } => (*low..*high).contains(&number),
985            MissingValueRange::From { low } => number >= *low,
986            MissingValueRange::To { high } => number <= *high,
987        }
988    }
989
990    pub fn resize(&self, width: VarWidth) -> Result<(), MissingValuesError> {
991        if width.is_numeric() {
992            Ok(())
993        } else {
994            Err(MissingValuesError::MixedTypes)
995        }
996    }
997}
998
999impl Display for MissingValueRange {
1000    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1001        match self.low() {
1002            Some(low) => low.display_plain().fmt(f)?,
1003            None => write!(f, "LOW")?,
1004        }
1005
1006        write!(f, " THRU ")?;
1007
1008        match self.high() {
1009            Some(high) => high.display_plain().fmt(f)?,
1010            None => write!(f, "HIGH")?,
1011        }
1012        Ok(())
1013    }
1014}
1015
1016#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
1017pub enum Alignment {
1018    Left,
1019    Right,
1020    Center,
1021}
1022
1023impl Alignment {
1024    pub fn default_for_type(var_type: VarType) -> Self {
1025        match var_type {
1026            VarType::Numeric => Self::Right,
1027            VarType::String => Self::Left,
1028        }
1029    }
1030
1031    pub fn as_str(&self) -> &'static str {
1032        match self {
1033            Alignment::Left => "Left",
1034            Alignment::Right => "Right",
1035            Alignment::Center => "Center",
1036        }
1037    }
1038}
1039
1040/// [Level of measurement](https://en.wikipedia.org/wiki/Level_of_measurement).
1041#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
1042pub enum Measure {
1043    /// Nominal values can only be compared for equality.
1044    Nominal,
1045
1046    /// Ordinal values can be meaningfully ordered.
1047    Ordinal,
1048
1049    /// Scale values can be meaningfully compared for the degree of difference.
1050    Scale,
1051}
1052
1053impl Measure {
1054    pub fn default_for_type(var_type: VarType) -> Option<Measure> {
1055        match var_type {
1056            VarType::Numeric => None,
1057            VarType::String => Some(Self::Nominal),
1058        }
1059    }
1060
1061    pub fn as_str(&self) -> &'static str {
1062        match self {
1063            Measure::Nominal => "Nominal",
1064            Measure::Ordinal => "Ordinal",
1065            Measure::Scale => "Scale",
1066        }
1067    }
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072    use encoding_rs::{UTF_8, WINDOWS_1252};
1073
1074    use crate::{
1075        data::{ByteString, Datum, RawString, WithEncoding},
1076        variable::{MissingValues, ValueLabels, VarWidth},
1077    };
1078
1079    #[test]
1080    fn var_width_codepage_to_unicode() {
1081        fn check_unicode(input: VarWidth, expected: VarWidth) {
1082            let mut actual = input;
1083            actual.codepage_to_unicode();
1084            assert_eq!(actual, expected);
1085        }
1086
1087        check_unicode(VarWidth::Numeric, VarWidth::Numeric);
1088        check_unicode(VarWidth::String(1), VarWidth::String(3));
1089        check_unicode(VarWidth::String(2), VarWidth::String(6));
1090        check_unicode(VarWidth::String(3), VarWidth::String(9));
1091        check_unicode(VarWidth::String(1000), VarWidth::String(3000));
1092        check_unicode(VarWidth::String(20000), VarWidth::String(32767));
1093        check_unicode(VarWidth::String(30000), VarWidth::String(32767));
1094    }
1095
1096    #[test]
1097    fn missing_values_codepage_to_unicode() {
1098        fn windows_1252(s: &str) -> WithEncoding<ByteString> {
1099            ByteString::from(WINDOWS_1252.encode(s).0).with_encoding(WINDOWS_1252)
1100        }
1101
1102        let mut actual = MissingValues::new(
1103            vec![
1104                Datum::String(windows_1252("abcdefgh")),
1105                Datum::String(windows_1252("éèäî   ")),
1106                Datum::String(windows_1252("aaéèäîdf")),
1107            ],
1108            None,
1109        )
1110        .unwrap();
1111        actual.codepage_to_unicode();
1112
1113        fn utf_8(s: &str) -> WithEncoding<ByteString> {
1114            ByteString::from(s).with_encoding(UTF_8)
1115        }
1116
1117        let expected = MissingValues::new(
1118            vec![
1119                Datum::String(utf_8("abcdefgh")),
1120                Datum::String(utf_8("éèäî")),
1121                Datum::String(utf_8("aaéèä")),
1122            ],
1123            None,
1124        )
1125        .unwrap();
1126
1127        assert_eq!(&actual, &expected);
1128    }
1129
1130    #[test]
1131    fn value_labels_codepage_to_unicode() {
1132        fn windows_1252(s: &str) -> Datum<ByteString> {
1133            Datum::String(ByteString::from(WINDOWS_1252.encode(s).0))
1134        }
1135
1136        let mut actual = ValueLabels::new();
1137        actual.insert(windows_1252("abcd"), "Label 1");
1138        actual.insert(windows_1252("éèäî"), "Label 2");
1139        actual.codepage_to_unicode(WINDOWS_1252);
1140
1141        let mut expected = ValueLabels::new();
1142        expected.insert(Datum::String(ByteString::from("abcd        ")), "Label 1");
1143        expected.insert(Datum::String(ByteString::from("éèäî    ")), "Label 2");
1144
1145        assert_eq!(&actual, &expected);
1146    }
1147}