Skip to main content

sim_citizen/
field.rs

1//! The CitizenField trait for encoding and decoding citizen field values.
2
3use std::convert::TryFrom;
4
5use sim_kernel::{CapabilityName, Cx, Error, Expr, NumberLiteral, Result, Symbol, Value};
6use sim_value::kind::expr_kind;
7
8/// Encodes and decodes a Rust type as one citizen constructor field.
9///
10/// Generated and hand-written citizens use this trait to project each field to
11/// an `Expr` for the constructor encoding and to recover it on read-construct.
12/// The kernel owns `Expr`/`Value`/`Cx`; sim-citizen provides the field codec
13/// and the standard scalar/collection implementations.
14///
15/// # Examples
16///
17/// The expr-level encode/decode pair round-trips without a context:
18///
19/// ```
20/// # use sim_citizen::CitizenField;
21/// let original: Vec<i64> = vec![1, 2, 3];
22/// let expr = original.encode_field();
23/// let decoded = Vec::<i64>::decode_field_expr(&expr, "numbers").unwrap();
24/// assert_eq!(decoded, original);
25/// ```
26pub trait CitizenField: Sized {
27    /// Encodes the field value as its constructor `Expr`.
28    fn encode_field(&self) -> Expr;
29    /// Decodes the field from a constructor `Expr`, naming `field` in errors.
30    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self>;
31
32    /// Decodes the field from a runtime [`Value`] via its `Expr` projection.
33    ///
34    /// Default implementation projects `value` to an `Expr` through `cx` and
35    /// delegates to [`CitizenField::decode_field_expr`].
36    fn decode_field_value(cx: &mut Cx, value: Value, field: &'static str) -> Result<Self> {
37        let expr = value_to_expr(cx, value, field)?;
38        Self::decode_field_expr(&expr, field)
39    }
40}
41
42/// Projects a runtime [`Value`] to an `Expr`, tagging errors with `field`.
43pub fn value_to_expr(cx: &mut Cx, value: Value, field: &'static str) -> Result<Expr> {
44    value
45        .object()
46        .as_expr(cx)
47        .map_err(|err| field_error(field, format!("cannot convert value to Expr: {err}")))
48}
49
50/// Builds a runtime [`Value`] from an `Expr` using `cx`'s factory.
51///
52/// Reconstructs literals, symbols, lists, and symbol-keyed maps, falling back
53/// to the factory's generic `expr` path for other shapes. Used to turn citizen
54/// constructor arguments back into values for read-construct.
55pub fn value_from_expr(cx: &mut Cx, expr: &Expr) -> Result<Value> {
56    match expr {
57        Expr::Nil => cx.factory().nil(),
58        Expr::Bool(value) => cx.factory().bool(*value),
59        Expr::Number(value) => cx
60            .factory()
61            .number_literal(value.domain.clone(), value.canonical.clone()),
62        Expr::Symbol(value) => cx.factory().symbol(value.clone()),
63        Expr::String(value) => cx.factory().string(value.clone()),
64        Expr::Bytes(value) => cx.factory().bytes(value.clone()),
65        Expr::List(items) => {
66            let values = items
67                .iter()
68                .map(|item| value_from_expr(cx, item))
69                .collect::<Result<Vec<_>>>()?;
70            cx.factory().list(values)
71        }
72        Expr::Map(entries) => {
73            let entries = entries
74                .iter()
75                .map(|(key, value)| {
76                    let Expr::Symbol(key) = key else {
77                        return Err(field_error(
78                            "map",
79                            format!("expected symbol key, found {}", expr_kind(key)),
80                        ));
81                    };
82                    Ok((key.clone(), value_from_expr(cx, value)?))
83                })
84                .collect::<Result<Vec<_>>>()?;
85            cx.factory().table(entries)
86        }
87        other => cx.factory().expr(other.clone()),
88    }
89}
90
91/// Checks that a citizen's version argument equals the `expected` version.
92///
93/// Citizen constructors carry a leading `v<N>` version symbol; this verifies it
94/// matches, returning an error naming `class` otherwise.
95pub fn decode_version(cx: &mut Cx, value: Value, expected: u32, class: Symbol) -> Result<()> {
96    let expected_symbol = Symbol::new(format!("v{expected}"));
97    match value_to_expr(cx, value, "version")? {
98        Expr::Symbol(actual) if actual == expected_symbol => Ok(()),
99        other => Err(Error::Eval(format!(
100            "citizen {class} expects version {expected_symbol}, found {}",
101            expr_kind(&other)
102        ))),
103    }
104}
105
106/// Builds the standard error for a wrong read-constructor argument count.
107pub fn arity_error(class: Symbol, expected: usize, actual: usize) -> Error {
108    Error::Eval(format!(
109        "citizen {class} expects {expected} read-constructor argument(s), found {actual}"
110    ))
111}
112
113/// Builds a `citizen field <field>: <message>` evaluation error.
114pub fn field_error(field: &'static str, message: impl Into<String>) -> Error {
115    Error::Eval(format!("citizen field {field}: {}", message.into()))
116}
117
118fn expect_number_domain(
119    number: &NumberLiteral,
120    expected: Symbol,
121    field: &'static str,
122) -> Result<()> {
123    if number.domain == expected {
124        Ok(())
125    } else {
126        Err(field_error(
127            field,
128            format!("expected number domain {expected}, found {}", number.domain),
129        ))
130    }
131}
132
133macro_rules! signed_int_field {
134    ($($ty:ty),* $(,)?) => {
135        $(
136            impl CitizenField for $ty {
137                fn encode_field(&self) -> Expr {
138                    int_expr(self.to_string())
139                }
140
141                fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
142                    let value = decode_integer_text(expr, field)?;
143                    <$ty>::try_from(value).map_err(|_| {
144                        field_error(field, format!("integer {value} is out of range"))
145                    })
146                }
147            }
148        )*
149    };
150}
151
152macro_rules! unsigned_int_field {
153    ($($ty:ty),* $(,)?) => {
154        $(
155            impl CitizenField for $ty {
156                fn encode_field(&self) -> Expr {
157                    int_expr(self.to_string())
158                }
159
160                fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
161                    let value = decode_integer_text(expr, field)?;
162                    <$ty>::try_from(value).map_err(|_| {
163                        field_error(field, format!("integer {value} is out of range"))
164                    })
165                }
166            }
167        )*
168    };
169}
170
171signed_int_field!(i8, i16, i32, i64, i128, isize);
172unsigned_int_field!(u8, u16, u32, u64, usize);
173
174impl CitizenField for bool {
175    fn encode_field(&self) -> Expr {
176        Expr::Bool(*self)
177    }
178
179    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
180        match expr {
181            Expr::Bool(value) => Ok(*value),
182            other => Err(field_error(
183                field,
184                format!("expected bool, found {}", expr_kind(other)),
185            )),
186        }
187    }
188}
189
190impl CitizenField for String {
191    fn encode_field(&self) -> Expr {
192        Expr::String(self.clone())
193    }
194
195    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
196        match expr {
197            Expr::String(value) => Ok(value.clone()),
198            other => Err(field_error(
199                field,
200                format!("expected string, found {}", expr_kind(other)),
201            )),
202        }
203    }
204}
205
206impl CitizenField for Symbol {
207    fn encode_field(&self) -> Expr {
208        Expr::Symbol(self.clone())
209    }
210
211    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
212        match expr {
213            Expr::Symbol(value) => Ok(value.clone()),
214            other => Err(field_error(
215                field,
216                format!("expected symbol, found {}", expr_kind(other)),
217            )),
218        }
219    }
220}
221
222impl CitizenField for Expr {
223    fn encode_field(&self) -> Expr {
224        self.clone()
225    }
226
227    fn decode_field_expr(expr: &Expr, _field: &'static str) -> Result<Self> {
228        Ok(expr.clone())
229    }
230}
231
232impl CitizenField for CapabilityName {
233    fn encode_field(&self) -> Expr {
234        Expr::String(self.as_str().to_owned())
235    }
236
237    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
238        match expr {
239            Expr::String(value) => Ok(CapabilityName::new(value.clone())),
240            other => Err(field_error(
241                field,
242                format!(
243                    "expected string capability name, found {}",
244                    expr_kind(other)
245                ),
246            )),
247        }
248    }
249}
250
251impl CitizenField for f64 {
252    fn encode_field(&self) -> Expr {
253        Expr::Number(NumberLiteral {
254            domain: Symbol::qualified("numbers", "f64"),
255            canonical: canonical_f64(*self),
256        })
257    }
258
259    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
260        match expr {
261            Expr::Number(number) => {
262                expect_number_domain(number, Symbol::qualified("numbers", "f64"), field)?;
263                number
264                    .canonical
265                    .parse::<f64>()
266                    .map_err(|err| field_error(field, format!("invalid f64: {err}")))
267            }
268            other => Err(field_error(
269                field,
270                format!("expected number, found {}", expr_kind(other)),
271            )),
272        }
273    }
274}
275
276impl<A, B> CitizenField for (A, B)
277where
278    A: CitizenField,
279    B: CitizenField,
280{
281    fn encode_field(&self) -> Expr {
282        Expr::List(vec![self.0.encode_field(), self.1.encode_field()])
283    }
284
285    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
286        let Expr::List(items) = expr else {
287            return Err(field_error(
288                field,
289                format!("expected pair list, found {}", expr_kind(expr)),
290            ));
291        };
292        let [first, second] = items.as_slice() else {
293            return Err(field_error(
294                field,
295                format!("expected pair list with 2 item(s), found {}", items.len()),
296            ));
297        };
298        Ok((
299            A::decode_field_expr(first, field)?,
300            B::decode_field_expr(second, field)?,
301        ))
302    }
303}
304
305impl<T> CitizenField for Vec<T>
306where
307    T: CitizenField,
308{
309    fn encode_field(&self) -> Expr {
310        Expr::List(self.iter().map(CitizenField::encode_field).collect())
311    }
312
313    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
314        match expr {
315            Expr::List(items) => items
316                .iter()
317                .map(|item| T::decode_field_expr(item, field))
318                .collect(),
319            other => Err(field_error(
320                field,
321                format!("expected list, found {}", expr_kind(other)),
322            )),
323        }
324    }
325}
326
327impl<T> CitizenField for Option<T>
328where
329    T: CitizenField,
330{
331    fn encode_field(&self) -> Expr {
332        self.as_ref()
333            .map(CitizenField::encode_field)
334            .unwrap_or(Expr::Nil)
335    }
336
337    fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
338        match expr {
339            Expr::Nil => Ok(None),
340            other => T::decode_field_expr(other, field).map(Some),
341        }
342    }
343}
344
345fn int_expr(canonical: String) -> Expr {
346    Expr::Number(NumberLiteral {
347        domain: Symbol::qualified("citizen", "int"),
348        canonical,
349    })
350}
351
352fn decode_integer_text(expr: &Expr, field: &'static str) -> Result<i128> {
353    match expr {
354        Expr::Number(number) => {
355            expect_number_domain(number, Symbol::qualified("citizen", "int"), field)?;
356            number
357                .canonical
358                .parse::<i128>()
359                .map_err(|err| field_error(field, format!("invalid integer: {err}")))
360        }
361        other => Err(field_error(
362            field,
363            format!("expected integer number, found {}", expr_kind(other)),
364        )),
365    }
366}
367
368fn canonical_f64(value: f64) -> String {
369    if value.is_nan() {
370        "NaN".to_owned()
371    } else if value == f64::INFINITY {
372        "inf".to_owned()
373    } else if value == f64::NEG_INFINITY {
374        "-inf".to_owned()
375    } else {
376        value.to_string()
377    }
378}