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