70e251d08e181d81944d7ce06ff24c369b0ea2b8
[pspp] / rust / src / sack.rs
1 use anyhow::{anyhow, Result};
2 use float_next_after::NextAfter;
3 use num::Bounded;
4 use ordered_float::OrderedFloat;
5 use std::{fmt::Display, iter::Peekable, str::Chars};
6
7 use crate::endian::{Endian, ToBytes};
8
9 pub fn sack(input: &str, endian: Endian) -> Result<Vec<u8>> {
10     let mut lexer = Lexer::new(input, endian)?;
11     let mut output = Vec::new();
12     while parse_data_item(&mut lexer, &mut output)? {}
13     Ok(Vec::new())
14 }
15
16 fn parse_data_item(lexer: &mut Lexer, output: &mut Vec<u8>) -> Result<bool> {
17     if lexer.token.is_none() {
18         return Ok(false);
19     };
20     match lexer.take()? {
21         Token::Integer(integer) => output.extend_from_slice(&lexer.endian.to_bytes(integer)),
22         Token::Float(float) => output.extend_from_slice(&lexer.endian.to_bytes(float.0)),
23         Token::PcSysmis => {
24             output.extend_from_slice(&[0xf5, 0x1e, 0x26, 0x02, 0x8a, 0x8c, 0xed, 0xff])
25         }
26         Token::I8 => collect_integers::<u8, 1>(lexer, "i8", output)?,
27         Token::I16 => collect_integers::<u16, 2>(lexer, "i16", output)?,
28         Token::I64 => collect_integers::<i64, 8>(lexer, "i64", output)?,
29         _ => return Err(anyhow!("syntax error")),
30     }
31     Ok(true)
32 }
33
34 fn collect_integers<T, const N: usize>(
35     lexer: &mut Lexer,
36     name: &str,
37     output: &mut Vec<u8>,
38 ) -> Result<()>
39 where
40     T: Bounded + Display + TryFrom<i64> + Copy,
41     Endian: ToBytes<T, N>,
42 {
43     let mut n = 0;
44     while let Some(integer) = lexer.take_if(|t| match t {
45         Token::Integer(integer) => Some(*integer),
46         _ => None,
47     })? {
48         let Ok(integer) = integer.try_into() else {
49             return Err(anyhow!(
50                 "{integer} is not in the valid range [{},{}]",
51                 T::min_value(),
52                 T::max_value()
53             ));
54         };
55         output.extend_from_slice(&lexer.endian.to_bytes(integer));
56         n += 1;
57     }
58     if n == 0 {
59         return Err(anyhow!("integer expected after '{name}'"));
60     }
61     Ok(())
62 }
63
64 #[derive(PartialEq, Eq, Clone)]
65 enum Token {
66     Integer(i64),
67     Float(OrderedFloat<f64>),
68     PcSysmis,
69     String(String),
70     Semicolon,
71     Asterisk,
72     LParen,
73     RParen,
74     I8,
75     I16,
76     I64,
77     S(usize),
78     Count,
79     Count8,
80     Hex,
81     Label(String),
82     At(String),
83     Minus,
84     Plus,
85 }
86
87 struct Lexer<'a> {
88     iter: Peekable<Chars<'a>>,
89     token: Option<Token>,
90     line_number: usize,
91     endian: Endian,
92 }
93
94 impl<'a> Lexer<'a> {
95     fn new(input: &'a str, endian: Endian) -> Result<Lexer<'a>> {
96         let mut lexer = Lexer {
97             iter: input.chars().peekable(),
98             token: None,
99             line_number: 1,
100             endian,
101         };
102         lexer.next()?;
103         Ok(lexer)
104     }
105     fn take(&mut self) -> Result<Token> {
106         let Some(token) = self.token.take() else {
107             return Err(anyhow!("unexpected end of input"));
108         };
109         self.token = self.next()?;
110         Ok(token)
111     }
112     fn take_if<F, T>(&mut self, condition: F) -> Result<Option<T>>
113     where
114         F: FnOnce(&Token) -> Option<T>,
115     {
116         let Some(ref token) = self.token else {
117             return Ok(None);
118         };
119         match condition(&token) {
120             Some(value) => {
121                 self.token = self.next()?;
122                 Ok(Some(value))
123             }
124             None => Ok(None),
125         }
126     }
127     fn get(&'a mut self) -> Result<Option<&'a Token>> {
128         if self.token.is_none() {
129             Err(anyhow!("unexpected end of input"))
130         } else {
131             self.token = self.next()?;
132             Ok((&self.token).into())
133         }
134     }
135
136     fn next(&mut self) -> Result<Option<Token>> {
137         // Get the first character of the token, skipping past white space and
138         // comments.
139         let c = loop {
140             let Some(c) = self.iter.next() else {
141                 return Ok(None);
142             };
143             let c = if c == '#' {
144                 loop {
145                     match self.iter.next() {
146                         None => return Ok(None),
147                         Some('\n') => break,
148                         _ => (),
149                     }
150                 }
151                 '\n'
152             } else {
153                 c
154             };
155             if c == '\n' {
156                 self.line_number += 1
157             } else if !c.is_whitespace() && c != '<' && c != '>' {
158                 break c;
159             }
160         };
161
162         let token = match c {
163             c if c.is_ascii_digit() || c == '-' => {
164                 let mut s = String::from(c);
165                 while let Some(c) = self
166                     .iter
167                     .next_if(|&c| c.is_ascii_digit() || c.is_alphabetic() || c == '.')
168                 {
169                     s.push(c);
170                 }
171
172                 if s == "-" {
173                     Token::Minus
174                 } else if !s.contains('.') {
175                     Token::Integer(
176                         s.parse()
177                             .map_err(|msg| anyhow!("bad integer literal '{s}' ({msg})"))?,
178                     )
179                 } else {
180                     Token::Float(
181                         s.parse()
182                             .map_err(|msg| anyhow!("bad float literal '{s}' ({msg})"))?,
183                     )
184                 }
185             }
186             '"' => {
187                 let mut s = String::from(c);
188                 loop {
189                     match self.iter.next() {
190                         None => return Err(anyhow!("end-of-file inside string")),
191                         Some('\n') => return Err(anyhow!("new-line inside string")),
192                         Some('"') => break,
193                         Some(c) => s.push(c),
194                     }
195                 }
196                 Token::String(s)
197             }
198             ';' => Token::Semicolon,
199             '*' => Token::Asterisk,
200             '+' => Token::Plus,
201             '(' => Token::LParen,
202             ')' => Token::RParen,
203             c if c.is_alphabetic() || c == '@' || c == '_' => {
204                 let mut s = String::from(c);
205                 while let Some(c) = self
206                     .iter
207                     .next_if(|&c| c.is_ascii_digit() || c.is_alphabetic() || c == '.' || c == '_')
208                 {
209                     s.push(c);
210                 }
211                 if self.iter.next_if_eq(&':').is_some() {
212                     Token::Label(s)
213                 } else if s.starts_with('@') {
214                     Token::At(s)
215                 } else if let Some(count) = s.strip_prefix('s') {
216                     Token::S(
217                         count
218                             .parse()
219                             .map_err(|msg| anyhow!("bad counted string '{s}' ({msg})"))?,
220                     )
221                 } else {
222                     match &s[..] {
223                         "i8" => Token::I8,
224                         "i16" => Token::I16,
225                         "i64" => Token::I64,
226                         "SYSMIS" => Token::Float(OrderedFloat(-f64::MAX)),
227                         "PCSYSMIS" => Token::PcSysmis,
228                         "LOWEST" => Token::Float((-f64::MAX).next_after(0.0).into()),
229                         "HIGHEST" => Token::Float(f64::MAX.into()),
230                         "ENDIAN" => Token::Integer(if self.endian == Endian::Big { 1 } else { 2 }),
231                         "COUNT" => Token::Count,
232                         "COUNT8" => Token::Count8,
233                         "hex" => Token::Hex,
234                         _ => return Err(anyhow!("invalid token '{s}'")),
235                     }
236                 }
237             }
238             _ => return Err(anyhow!("invalid input byte '{c}'")),
239         };
240         Ok(Some(token))
241     }
242 }