Skip to main content

geop_ops_step/import/
reader.rs

1//! Typed access to the instances of an [`Exchange`]: an entity of an
2//! expected type and its parameters by position, every failure naming the
3//! instance by `#id` and type.
4
5use geop_core_math::geop_error::{GeopError, GeopResult};
6
7use crate::part21::{Exchange, Instance, Record, Value};
8
9/// The error for an instance the importer does not support: what it is,
10/// where, and why.
11pub fn unsupported(id: u64, instance: &Instance, why: &str) -> GeopError {
12    GeopError::new(format!(
13        "#{id} {} is not supported: {why}",
14        instance.type_name()
15    ))
16}
17
18/// One record of an instance, with its parameters.
19#[derive(Clone, Copy)]
20pub struct Args<'a> {
21    pub id: u64,
22    pub record: &'a Record,
23}
24
25impl<'a> Args<'a> {
26    fn error(&self, k: usize, what: &str) -> GeopError {
27        GeopError::new(format!(
28            "#{} {}: parameter {} {what}",
29            self.id,
30            self.record.name,
31            k + 1
32        ))
33    }
34
35    pub fn get(&self, k: usize) -> GeopResult<&'a Value> {
36        self.record
37            .args
38            .get(k)
39            .ok_or_else(|| self.error(k, "is missing"))
40    }
41
42    pub fn is_null(&self, k: usize) -> bool {
43        matches!(self.record.args.get(k), None | Some(Value::Null))
44    }
45
46    pub fn real(&self, k: usize) -> GeopResult<f64> {
47        real(self.get(k)?).ok_or_else(|| self.error(k, "is not a number"))
48    }
49
50    pub fn integer(&self, k: usize) -> GeopResult<i64> {
51        match self.get(k)? {
52            Value::Integer(i) => Ok(*i),
53            _ => Err(self.error(k, "is not an integer")),
54        }
55    }
56
57    pub fn reference(&self, k: usize) -> GeopResult<u64> {
58        match self.get(k)? {
59            Value::Ref(id) => Ok(*id),
60            _ => Err(self.error(k, "is not a reference to an instance")),
61        }
62    }
63
64    pub fn list(&self, k: usize) -> GeopResult<&'a [Value]> {
65        match self.get(k)? {
66            Value::List(values) => Ok(values),
67            _ => Err(self.error(k, "is not a list")),
68        }
69    }
70
71    pub fn references(&self, k: usize) -> GeopResult<Vec<u64>> {
72        self.list(k)?
73            .iter()
74            .map(|v| match v {
75                Value::Ref(id) => Ok(*id),
76                _ => Err(self.error(k, "is not a list of references")),
77            })
78            .collect()
79    }
80
81    pub fn reals(&self, k: usize) -> GeopResult<Vec<f64>> {
82        self.list(k)?
83            .iter()
84            .map(|v| real(v).ok_or_else(|| self.error(k, "is not a list of numbers")))
85            .collect()
86    }
87
88    pub fn integers(&self, k: usize) -> GeopResult<Vec<i64>> {
89        self.list(k)?
90            .iter()
91            .map(|v| match v {
92                Value::Integer(i) => Ok(*i),
93                _ => Err(self.error(k, "is not a list of integers")),
94            })
95            .collect()
96    }
97
98    /// A `BOOLEAN` or `LOGICAL`: `.T.` is true, `.F.` false.
99    pub fn logical(&self, k: usize) -> GeopResult<bool> {
100        match self.get(k)? {
101            Value::Enum(e) if e == "T" => Ok(true),
102            Value::Enum(e) if e == "F" => Ok(false),
103            _ => Err(self.error(k, "is not .T. or .F.")),
104        }
105    }
106
107    pub fn enumeration(&self, k: usize) -> GeopResult<&'a str> {
108        match self.get(k)? {
109            Value::Enum(e) => Ok(e),
110            _ => Err(self.error(k, "is not an enumeration value")),
111        }
112    }
113
114    pub fn string(&self, k: usize) -> GeopResult<&'a str> {
115        match self.get(k)? {
116            Value::String(s) => Ok(s),
117            _ => Err(self.error(k, "is not a string")),
118        }
119    }
120}
121
122/// A number, as written: a real, an integer, or a measure of either.
123pub fn real(value: &Value) -> Option<f64> {
124    match value {
125        Value::Real(r) => Some(*r),
126        Value::Integer(i) => Some(*i as f64),
127        Value::Typed(_, inner) => real(inner),
128        _ => None,
129    }
130}
131
132/// The instances of a file, read by type.
133#[derive(Clone, Copy)]
134pub struct Reader<'a> {
135    pub exchange: &'a Exchange,
136}
137
138impl<'a> Reader<'a> {
139    pub fn instance(&self, id: u64) -> GeopResult<&'a Instance> {
140        self.exchange.get(id)
141    }
142
143    /// The record `name` of the instance `#id`, which has to be of that
144    /// type.
145    pub fn args(&self, id: u64, name: &str) -> GeopResult<Args<'a>> {
146        let instance = self.instance(id)?;
147        let record = instance.record(name).ok_or_else(|| {
148            GeopError::new(format!(
149                "#{id} is a {}, where a {name} was expected",
150                instance.type_name()
151            ))
152        })?;
153        Ok(Args { id, record })
154    }
155
156    /// The record of the first of `names` the instance `#id` is of, and
157    /// which one it is.
158    pub fn args_of_any(
159        &self,
160        id: u64,
161        names: &[&'static str],
162    ) -> GeopResult<(&'static str, Args<'a>)> {
163        let instance = self.instance(id)?;
164        for &name in names {
165            if let Some(record) = instance.record(name) {
166                return Ok((name, Args { id, record }));
167            }
168        }
169        Err(GeopError::new(format!(
170            "#{id} is a {}, where one of {} was expected",
171            instance.type_name(),
172            names.join(", ")
173        )))
174    }
175}