decodedrecord works
[pspp] / rust / src / dictionary.rs
index 4a8e272116a4d667151c8f06f05b8ec1f6986e37..6662aaa94e5249a3933cba5e42bcb56c9123c093 100644 (file)
 use std::{
     collections::{HashMap, HashSet},
     fmt::Debug,
-    ops::{Bound, RangeBounds},
+    ops::{Bound, RangeBounds}, cmp::Ordering,
 };
 
 use encoding_rs::Encoding;
 use indexmap::IndexSet;
+use num::integer::div_ceil;
+use ordered_float::OrderedFloat;
 
 use crate::{
-    cooked::{Value, VarWidth},
     format::Spec,
     identifier::{ByIdentifier, HasIdentifier, Identifier},
-    raw::{Alignment, CategoryLabels, Measure, MissingValues, VarType},
+    raw::{Alignment, CategoryLabels, Measure, MissingValues, VarType, self, RawStr, Decoder},
 };
 
 pub type DictIndex = usize;
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum VarWidth {
+    Numeric,
+    String(u16),
+}
+
+impl PartialOrd for VarWidth {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        match (self, other) {
+            (VarWidth::Numeric, VarWidth::Numeric) => Some(Ordering::Equal),
+            (VarWidth::String(a), VarWidth::String(b)) => Some(a.cmp(b)),
+            _ => None,
+        }
+    }
+}
+
+impl VarWidth {
+    pub 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<VarWidth>,
+        b: Option<VarWidth>,
+        f: impl Fn(u16, u16) -> u16,
+    ) -> Option<VarWidth> {
+        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<VarWidth>, b: Option<VarWidth>) -> Option<VarWidth> {
+        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<VarWidth>, b: Option<VarWidth>) -> Option<VarWidth> {
+        Self::width_predicate(a, b, |a, b| a.min(b))
+    }
+
+    pub fn default_display_width(&self) -> u32 {
+        match self {
+            VarWidth::Numeric => 8,
+            VarWidth::String(width) => *width.min(&32) as u32,
+        }
+    }
+}
+
+impl From<VarWidth> for VarType {
+    fn from(source: VarWidth) -> Self {
+        match source {
+            VarWidth::Numeric => VarType::Numeric,
+            VarWidth::String(_) => VarType::String,
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Value {
+    Number(Option<OrderedFloat<f64>>),
+    String(String),
+}
+
+impl Value {
+    pub fn decode(raw: &raw::Value<RawStr<8>>, decoder: &Decoder) -> Self {
+        match raw {
+            raw::Value::Number(x) => Value::Number(x.map(|x| x.into())),
+            raw::Value::String(s) => Value::String(decoder.decode_exact_length(&s.0).into()),
+        }
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct Dictionary {
     pub variables: IndexSet<ByIdentifier<Variable>>,