work
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 6 Aug 2023 17:12:52 +0000 (10:12 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 6 Aug 2023 17:12:52 +0000 (10:12 -0700)
rust/src/raw.rs

index fccc33b41bb6d58bc588502d0a3600e9b12e0600..f0e8c540c2ac170f6fe18682bd8358ba817bc85d 100644 (file)
@@ -779,12 +779,12 @@ where
 }
 
 pub struct IntegerInfo {
-    version: (i32, i32, i32),
-    machine_code: i32,
-    floating_point_rep: i32,
-    compression_code: i32,
-    endianness: i32,
-    character_code: i32,
+    pub version: (i32, i32, i32),
+    pub machine_code: i32,
+    pub floating_point_rep: i32,
+    pub compression_code: i32,
+    pub endianness: i32,
+    pub character_code: i32,
 }
 
 impl ExtensionRecord for IntegerInfo {
@@ -811,9 +811,9 @@ impl ExtensionRecord for IntegerInfo {
 }
 
 pub struct FloatInfo {
-    sysmis: f64,
-    highest: f64,
-    lowest: f64,
+    pub sysmis: f64,
+    pub highest: f64,
+    pub lowest: f64,
 }
 
 impl ExtensionRecord for FloatInfo {
@@ -1055,8 +1055,8 @@ impl TextRecord for LongVariableNameRecord {
 }
 
 pub struct VeryLongString {
-    short_name: String,
-    length: usize,
+    pub short_name: String,
+    pub length: usize,
 }
 
 impl VeryLongString {
@@ -1203,12 +1203,133 @@ impl ExtensionRecord for Encoding {
     }
 }
 
+pub struct Attribute {
+    pub name: String,
+    pub values: Vec<String>,
+}
+
+impl Attribute {
+    fn parse<'a>(input: &'a str, warn: &impl Fn(Error)) -> Result<(Attribute, &'a str), Error> {
+        let Some((name, mut input)) = input.split_once('(') else {
+            return Err(Error::TBD);
+        };
+        let mut values = Vec::new();
+        loop {
+            let Some((value, rest)) = input.split_once('\n') else {
+                return Err(Error::TBD);
+            };
+            if let Some(stripped) = value
+                .strip_prefix('\'')
+                .and_then(|value| value.strip_suffix('\''))
+            {
+                values.push(stripped.into());
+            } else {
+                warn(Error::TBD);
+                values.push(value.into());
+            }
+            if let Some(rest) = rest.strip_prefix(')') {
+                return Ok((
+                    Attribute {
+                        name: name.into(),
+                        values,
+                    },
+                    rest,
+                ));
+            }
+            input = rest;
+        }
+    }
+}
+
+pub struct AttributeSet(pub Vec<Attribute>);
+
+impl AttributeSet {
+    fn parse<'a>(
+        mut input: &'a str,
+        sentinel: Option<char>,
+        warn: &impl Fn(Error),
+    ) -> Result<(AttributeSet, &'a str), Error> {
+        let mut attributes = Vec::new();
+        let rest = loop {
+            match input.chars().next() {
+                None => break input,
+                c if c == sentinel => break &input[1..],
+                _ => {
+                    let (attribute, rest) = Attribute::parse(input, &warn)?;
+                    attributes.push(attribute);
+                    input = rest;
+                }
+            }
+        };
+        Ok((AttributeSet(attributes), rest))
+    }
+}
+
+pub struct FileAttributeRecord(AttributeSet);
+
+impl TextRecord for FileAttributeRecord {
+    const NAME: &'static str = "data file attributes";
+    fn parse(input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
+        let (set, rest) = AttributeSet::parse(input, None, &warn)?;
+        if !rest.is_empty() {
+            warn(Error::TBD);
+        }
+        Ok(FileAttributeRecord(set))
+    }
+}
+
+pub struct VarAttributeSet {
+    pub long_var_name: String,
+    pub attributes: AttributeSet,
+}
+
+impl VarAttributeSet {
+    fn parse<'a>(
+        input: &'a str,
+        warn: &impl Fn(Error),
+    ) -> Result<(VarAttributeSet, &'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(),
+                attributes,
+            },
+            rest,
+        ))
+    }
+}
+
+pub struct VariableAttributeRecord(Vec<VarAttributeSet>);
+
+impl TextRecord for VariableAttributeRecord {
+    const NAME: &'static str = "variable attributes";
+    fn parse(mut input: &str, warn: impl Fn(Error)) -> Result<Self, Error> {
+        let mut var_attribute_sets = Vec::new();
+        while !input.is_empty() {
+            match VarAttributeSet::parse(input, &warn) {
+                Ok((var_attribute, rest)) => {
+                    var_attribute_sets.push(var_attribute);
+                    input = rest;
+                }
+                Err(error) => {
+                    warn(error);
+                    break;
+                }
+            }
+        }
+        Ok(VariableAttributeRecord(var_attribute_sets))
+    }
+}
+
 pub struct NumberOfCasesRecord {
     /// Always observed as 1.
-    one: u64,
+    pub one: u64,
 
     /// Number of cases.
-    n_cases: u64,
+    pub n_cases: u64,
 }
 
 impl ExtensionRecord for NumberOfCasesRecord {