Skip to main content

lox_io/
spice.rs

1// SPDX-FileCopyrightText: 2023 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use std::collections::HashMap;
6
7use nom::branch::alt;
8use nom::bytes::complete::{tag, take_until, take_while, take_while1};
9use nom::character::complete::{alpha1, digit1, line_ending, multispace0, one_of};
10use nom::combinator::{map, map_res, recognize, rest};
11use nom::error::Error;
12use nom::multi::{fold_many1, many0, many1};
13use nom::number::complete::{double, float};
14use nom::sequence::{delimited, preceded, separated_pair, terminated};
15use nom::{Finish, IResult, Parser};
16use thiserror::Error;
17
18pub mod lsk;
19
20#[derive(Debug, Error, PartialEq)]
21#[error(transparent)]
22pub struct KernelError(#[from] Error<String>);
23
24#[derive(Clone, Debug, PartialEq)]
25enum Value {
26    Double(f64),
27    String(String),
28    Timestamp(String),
29    DoubleArray(Vec<f64>),
30    StringArray(Vec<String>),
31    TimestampArray(Vec<String>),
32}
33
34#[derive(Clone, Debug, PartialEq)]
35pub struct Kernel {
36    type_id: String,
37    items: HashMap<String, Value>,
38}
39
40type Entries = Vec<(String, Value)>;
41
42impl Kernel {
43    pub fn from_string(input: &str) -> Result<Self, KernelError> {
44        let result = kernel(input).map_err(|e| e.to_owned()).finish();
45        match result {
46            Ok((_, (type_id, entries, _))) => Ok(Self {
47                type_id: type_id.to_string(),
48                items: entries.into_iter().collect(),
49            }),
50            Err(err) => Err(KernelError(err)),
51        }
52    }
53
54    pub fn type_id(&self) -> &str {
55        &self.type_id
56    }
57
58    pub fn get_double(&self, key: &str) -> Option<f64> {
59        let value = self.items.get(key)?;
60        if let Value::Double(v) = value {
61            Some(*v)
62        } else {
63            None
64        }
65    }
66
67    pub fn get_double_array(&self, key: &str) -> Option<&Vec<f64>> {
68        let value = self.items.get(key)?;
69        if let Value::DoubleArray(v) = value {
70            Some(v)
71        } else {
72            None
73        }
74    }
75
76    pub fn get_timestamp_array(&self, key: &str) -> Option<&Vec<String>> {
77        let value = self.items.get(key)?;
78        if let Value::TimestampArray(v) = value {
79            Some(v)
80        } else {
81            None
82        }
83    }
84
85    pub fn keys(&self) -> Vec<&String> {
86        self.items.keys().collect()
87    }
88}
89
90fn kernel(s: &str) -> IResult<&str, (&str, Entries, &str)> {
91    let header = preceded(tag("KPL/"), alpha1);
92    let mut parser = (
93        header,
94        fold_many1(
95            preceded(
96                alt((take_until("\\begindata\n"), take_until("\\begindata\r"))),
97                data_block,
98            ),
99            Vec::new,
100            |mut out: Entries, item: Entries| {
101                out.extend(item);
102                out
103            },
104        ),
105        rest,
106    );
107    parser.parse(s)
108}
109
110fn fortran_double(s: &str) -> IResult<&str, f64> {
111    let mut parser = map_res(recognize((double, one_of("dD"), float)), |s: &str| {
112        str::replace(s, ['d', 'D'], "e").parse()
113    });
114    parser.parse(s)
115}
116
117fn spice_double(s: &str) -> IResult<&str, f64> {
118    let mut parser = alt((fortran_double, double));
119    parser.parse(s)
120}
121
122fn spice_string(s: &str) -> IResult<&str, String> {
123    let mut parser = fold_many1(
124        delimited(tag("'"), take_until("'"), tag("'")),
125        String::new,
126        |mut out: String, item: &str| {
127            if !out.is_empty() {
128                out.push('\'');
129            }
130            out.push_str(item);
131            out
132        },
133    );
134    parser.parse(s)
135}
136
137fn timestamp(s: &str) -> IResult<&str, String> {
138    let mut parser = map(
139        // NASA NAIF's LSK kernels break their own rules and mix timestamps with integers within a
140        // single array.
141        // See: https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/kernel.html#Variable%20Value%20Rules
142        alt((
143            preceded(tag("@"), take_while1(|c| !is_separator(c))),
144            digit1,
145        )),
146        String::from,
147    );
148    parser.parse(s)
149}
150
151fn is_separator(c: char) -> bool {
152    c.is_whitespace() || c == ','
153}
154
155fn separator(s: &str) -> IResult<&str, &str> {
156    take_while1(is_separator)(s)
157}
158
159fn double_array(s: &str) -> IResult<&str, Value> {
160    let mut parser = map(
161        delimited(
162            terminated(tag("("), separator),
163            many1(terminated(spice_double, separator)),
164            tag(")"),
165        ),
166        Value::DoubleArray,
167    );
168    parser.parse(s)
169}
170
171fn string_array(s: &str) -> IResult<&str, Value> {
172    let mut parser = map(
173        delimited(
174            terminated(tag("("), separator),
175            many1(terminated(spice_string, separator)),
176            tag(")"),
177        ),
178        Value::StringArray,
179    );
180    parser.parse(s)
181}
182
183fn timestamp_array(s: &str) -> IResult<&str, Value> {
184    let mut parser = map(
185        delimited(
186            terminated(tag("("), separator),
187            many1(terminated(timestamp, separator)),
188            tag(")"),
189        ),
190        Value::TimestampArray,
191    );
192    parser.parse(s)
193}
194
195fn double_value(s: &str) -> IResult<&str, Value> {
196    let mut parser = map(spice_double, Value::Double);
197    parser.parse(s)
198}
199
200fn string_value(s: &str) -> IResult<&str, Value> {
201    let mut parser = map(spice_string, Value::String);
202    parser.parse(s)
203}
204
205fn timestamp_value(s: &str) -> IResult<&str, Value> {
206    let mut parser = map(timestamp, Value::Timestamp);
207    parser.parse(s)
208}
209
210fn array_value(s: &str) -> IResult<&str, Value> {
211    let mut parser = alt((double_array, string_array, timestamp_array));
212    parser.parse(s)
213}
214
215fn key_value(s: &str) -> IResult<&str, (String, Value)> {
216    let mut parser = map(
217        separated_pair(
218            terminated(
219                take_while1(|x: char| !x.is_whitespace() && x != '='),
220                take_while(char::is_whitespace),
221            ),
222            terminated(tag("="), take_while1(char::is_whitespace)),
223            alt((double_value, string_value, timestamp_value, array_value)),
224        ),
225        |kv: (&str, Value)| (kv.0.to_string(), kv.1),
226    );
227    parser.parse(s)
228}
229
230fn start_tag(s: &str) -> IResult<&str, &str> {
231    let mut parser = terminated(tag("\\begindata"), line_ending);
232    parser.parse(s)
233}
234
235fn end_tag(s: &str) -> IResult<&str, &str> {
236    let mut parser = tag("\\begintext");
237    parser.parse(s)
238}
239
240fn data_block(s: &str) -> IResult<&str, Entries> {
241    let mut parser = delimited(
242        start_tag,
243        many0(preceded(multispace0, key_value)),
244        preceded(multispace0, end_tag),
245    );
246    parser.parse(s)
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn parse_err() {
255        let kernel = Kernel::from_string("foo");
256        assert!(kernel.is_err());
257    }
258
259    #[test]
260    fn test_double() {
261        assert_eq!(spice_double("6.3781366e3"), Ok(("", 6378.1366)));
262        assert_eq!(spice_double("+6378.1366"), Ok(("", 6378.1366)));
263        assert_eq!(spice_double("6.3781366D3"), Ok(("", 6378.1366)));
264        assert_eq!(spice_double("6.3781366d3"), Ok(("", 6378.1366)));
265        assert_eq!(spice_double("6.3781366E3"), Ok(("", 6378.1366)));
266        assert_eq!(spice_double("6378"), Ok(("", 6378.0)));
267
268        assert_eq!(
269            double_value("6.3781366e3"),
270            Ok(("", Value::Double(6378.1366)))
271        );
272
273        assert_eq!(spice_double("11e-1"), Ok(("", 1.1)));
274        assert_eq!(spice_double("123E-02"), Ok(("", 1.23)));
275        assert_eq!(spice_double("123K-01"), Ok(("K-01", 123.0)));
276        assert!(spice_double("abc").is_err());
277    }
278
279    #[test]
280    fn test_string() {
281        assert_eq!(
282            spice_string("'KILOMETERS'"),
283            Ok(("", "KILOMETERS".to_string()))
284        );
285        assert_eq!(
286            string_value("'KILOMETERS'"),
287            Ok(("", Value::String("KILOMETERS".to_string())))
288        );
289        assert_eq!(
290            spice_string("'You can''t always get what you want.'"),
291            Ok(("", "You can't always get what you want.".to_string()))
292        );
293    }
294
295    #[test]
296    fn test_timestamp() {
297        assert_eq!(timestamp("@1972-JAN-1"), Ok(("", "1972-JAN-1".to_string())));
298    }
299
300    #[test]
301    fn test_separator() {
302        assert_eq!(separator("   "), Ok(("", "   ")));
303        assert_eq!(separator(" , "), Ok(("", " , ")));
304        assert!(separator("foo").is_err());
305    }
306
307    #[test]
308    fn test_double_array() {
309        assert_eq!(
310            double_array("( 6378.1366     6378.1366     6356.7519   )"),
311            Ok((
312                "",
313                Value::DoubleArray(vec!(6378.1366, 6378.1366, 6356.7519))
314            ))
315        );
316        assert_eq!(
317            double_array("( 6378.1366, 6378.1366, 6356.7519 )"),
318            Ok((
319                "",
320                Value::DoubleArray(vec!(6378.1366, 6378.1366, 6356.7519))
321            ))
322        );
323        assert_eq!(
324            double_array("( 2.2031868551400003E+04 )"),
325            Ok(("", Value::DoubleArray(vec!(2.2031868551400003e4))))
326        )
327    }
328
329    #[test]
330    fn test_string_array() {
331        let input = "( 'KILOMETERS','SECONDS' \
332            'KILOMETERS/SECOND' )";
333        assert_eq!(
334            string_array(input),
335            Ok((
336                "",
337                Value::StringArray(vec!(
338                    "KILOMETERS".to_string(),
339                    "SECONDS".to_string(),
340                    "KILOMETERS/SECOND".to_string()
341                ))
342            ))
343        );
344    }
345
346    #[test]
347    fn test_timestamp_array() {
348        let input = "( @1972-JAN-1,@1972-JAN-1 \
349            @1972-JAN-1 )";
350        assert_eq!(
351            timestamp_array(input),
352            Ok((
353                "",
354                Value::TimestampArray(vec!(
355                    "1972-JAN-1".to_string(),
356                    "1972-JAN-1".to_string(),
357                    "1972-JAN-1".to_string()
358                ))
359            ))
360        );
361    }
362
363    #[test]
364    fn test_array() {
365        let exp_float = Value::DoubleArray(vec![6378.1366, 6378.1366, 6356.7519]);
366        let exp_string = Value::StringArray(vec![
367            "KILOMETERS".to_string(),
368            "SECONDS".to_string(),
369            "KILOMETERS/SECOND".to_string(),
370        ]);
371        assert_ne!(Value::Double(3.0), Value::Double(3.1));
372        assert_ne!(exp_float, exp_string);
373        assert_ne!(exp_string, exp_float);
374        assert_eq!(
375            array_value("( 6378.1366, 6378.1366, 6356.7519 )"),
376            Ok(("", exp_float))
377        );
378        let input = "( 'KILOMETERS','SECONDS' \
379            'KILOMETERS/SECOND' )";
380        assert_eq!(array_value(input), Ok(("", exp_string)));
381    }
382
383    #[test]
384    fn test_key_value() {
385        let input = "BODY399_RADII     = ( 6378.1366     6378.1366     6356.7519   )";
386        let exp_value = Value::DoubleArray(vec![6378.1366, 6378.1366, 6356.7519]);
387        let exp_key = "BODY399_RADII".to_string();
388        assert_eq!(key_value(input), Ok(("", (exp_key, exp_value))));
389        let input = "BODY1_GM       = ( 2.2031868551400003E+04 )";
390        let exp_value = Value::DoubleArray(vec![2.2031868551400003e4]);
391        let exp_key = "BODY1_GM".to_string();
392        assert_eq!(key_value(input), Ok(("", (exp_key, exp_value))));
393    }
394
395    #[test]
396    fn test_data_block() {
397        assert_eq!(start_tag("\\begindata\n"), Ok(("", "\\begindata")));
398        assert!(start_tag("foo \\begindata bar\n").is_err());
399
400        let block = "\\begindata
401
402        BODY499_POLE_RA          = (  317.269202  -0.10927547        0.  )
403        BODY499_POLE_DEC         = (   54.432516  -0.05827105        0.  )
404        BODY499_PM               = (  176.049863  +350.891982443297  0.  )
405
406        BODY499_NUT_PREC_RA      = (  0     0     0     0     0
407                                      0     0     0     0     0
408                                      0.000068
409                                      0.000238
410                                      0.000052
411                                      0.000009
412                                      0.419057                  )
413
414
415        BODY499_NUT_PREC_DEC     = (  0     0     0     0     0
416                                      0     0     0     0     0
417                                      0     0     0     0     0
418                                      0.000051
419                                      0.000141
420                                      0.000031
421                                      0.000005
422                                      1.591274                  )
423
424
425        BODY499_NUT_PREC_PM      = (  0     0     0     0     0
426                                      0     0     0     0     0
427                                      0     0     0     0     0
428                                      0     0     0     0     0
429                                      0.000145
430                                      0.000157
431                                      0.000040
432                                      0.000001
433                                      0.000001
434                                      0.584542                  )
435
436        \\begintext";
437        let k1 = "BODY499_POLE_RA".to_string();
438        let v1 = Value::DoubleArray(vec![317.269202, -0.10927547, 0.]);
439        let k2 = "BODY499_POLE_DEC".to_string();
440        let v2 = Value::DoubleArray(vec![54.432516, -0.05827105, 0.]);
441        let k3 = "BODY499_PM".to_string();
442        let v3 = Value::DoubleArray(vec![176.049863, 350.891982443297, 0.]);
443        let k4 = "BODY499_NUT_PREC_RA".to_string();
444        let v4 = Value::DoubleArray(vec![
445            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.000068, 0.000238, 0.000052,
446            0.000009, 0.419057,
447        ]);
448        let k5 = "BODY499_NUT_PREC_DEC".to_string();
449        let v5 = Value::DoubleArray(vec![
450            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.000051,
451            0.000141, 0.000031, 0.000005, 1.591274,
452        ]);
453        let k6 = "BODY499_NUT_PREC_PM".to_string();
454        let v6 = Value::DoubleArray(vec![
455            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
456            0.0, 0.0, 0.0, 0.000145, 0.000157, 0.000040, 0.000001, 0.000001, 0.584542,
457        ]);
458        let exp = vec![(k1, v1), (k2, v2), (k3, v3), (k4, v4), (k5, v5), (k6, v6)];
459        assert_eq!(data_block(block), Ok(("", exp)));
460    }
461}