sack making progress!
[pspp] / rust / src / sack.rs
index 6be81d8aff1c285473660c7ce75745243102e12f..70e251d08e181d81944d7ce06ff24c369b0ea2b8 100644 (file)
@@ -1,18 +1,70 @@
 use anyhow::{anyhow, Result};
 use float_next_after::NextAfter;
-use std::{iter::Peekable, str::Chars};
+use num::Bounded;
+use ordered_float::OrderedFloat;
+use std::{fmt::Display, iter::Peekable, str::Chars};
 
-use crate::endian::Endian;
+use crate::endian::{Endian, ToBytes};
 
 pub fn sack(input: &str, endian: Endian) -> Result<Vec<u8>> {
-    let lexer = Lexer::new(input, endian)?;
-    //let mut output = Vec::new();
+    let mut lexer = Lexer::new(input, endian)?;
+    let mut output = Vec::new();
+    while parse_data_item(&mut lexer, &mut output)? {}
     Ok(Vec::new())
 }
 
+fn parse_data_item(lexer: &mut Lexer, output: &mut Vec<u8>) -> Result<bool> {
+    if lexer.token.is_none() {
+        return Ok(false);
+    };
+    match lexer.take()? {
+        Token::Integer(integer) => output.extend_from_slice(&lexer.endian.to_bytes(integer)),
+        Token::Float(float) => output.extend_from_slice(&lexer.endian.to_bytes(float.0)),
+        Token::PcSysmis => {
+            output.extend_from_slice(&[0xf5, 0x1e, 0x26, 0x02, 0x8a, 0x8c, 0xed, 0xff])
+        }
+        Token::I8 => collect_integers::<u8, 1>(lexer, "i8", output)?,
+        Token::I16 => collect_integers::<u16, 2>(lexer, "i16", output)?,
+        Token::I64 => collect_integers::<i64, 8>(lexer, "i64", output)?,
+        _ => return Err(anyhow!("syntax error")),
+    }
+    Ok(true)
+}
+
+fn collect_integers<T, const N: usize>(
+    lexer: &mut Lexer,
+    name: &str,
+    output: &mut Vec<u8>,
+) -> Result<()>
+where
+    T: Bounded + Display + TryFrom<i64> + Copy,
+    Endian: ToBytes<T, N>,
+{
+    let mut n = 0;
+    while let Some(integer) = lexer.take_if(|t| match t {
+        Token::Integer(integer) => Some(*integer),
+        _ => None,
+    })? {
+        let Ok(integer) = integer.try_into() else {
+            return Err(anyhow!(
+                "{integer} is not in the valid range [{},{}]",
+                T::min_value(),
+                T::max_value()
+            ));
+        };
+        output.extend_from_slice(&lexer.endian.to_bytes(integer));
+        n += 1;
+    }
+    if n == 0 {
+        return Err(anyhow!("integer expected after '{name}'"));
+    }
+    Ok(())
+}
+
+#[derive(PartialEq, Eq, Clone)]
 enum Token {
     Integer(i64),
-    Float(f64),
+    Float(OrderedFloat<f64>),
     PcSysmis,
     String(String),
     Semicolon,
@@ -50,6 +102,28 @@ impl<'a> Lexer<'a> {
         lexer.next()?;
         Ok(lexer)
     }
+    fn take(&mut self) -> Result<Token> {
+        let Some(token) = self.token.take() else {
+            return Err(anyhow!("unexpected end of input"));
+        };
+        self.token = self.next()?;
+        Ok(token)
+    }
+    fn take_if<F, T>(&mut self, condition: F) -> Result<Option<T>>
+    where
+        F: FnOnce(&Token) -> Option<T>,
+    {
+        let Some(ref token) = self.token else {
+            return Ok(None);
+        };
+        match condition(&token) {
+            Some(value) => {
+                self.token = self.next()?;
+                Ok(Some(value))
+            }
+            None => Ok(None),
+        }
+    }
     fn get(&'a mut self) -> Result<Option<&'a Token>> {
         if self.token.is_none() {
             Err(anyhow!("unexpected end of input"))
@@ -149,10 +223,10 @@ impl<'a> Lexer<'a> {
                         "i8" => Token::I8,
                         "i16" => Token::I16,
                         "i64" => Token::I64,
-                        "SYSMIS" => Token::Float(-f64::MAX),
+                        "SYSMIS" => Token::Float(OrderedFloat(-f64::MAX)),
                         "PCSYSMIS" => Token::PcSysmis,
-                        "LOWEST" => Token::Float((-f64::MAX).next_after(0.0)),
-                        "HIGHEST" => Token::Float(f64::MAX),
+                        "LOWEST" => Token::Float((-f64::MAX).next_after(0.0).into()),
+                        "HIGHEST" => Token::Float(f64::MAX.into()),
                         "ENDIAN" => Token::Integer(if self.endian == Endian::Big { 1 } else { 2 }),
                         "COUNT" => Token::Count,
                         "COUNT8" => Token::Count8,