Skip to main content

sim_lib_standard_core/
fidelity.rs

1//! Fidelity badges recording how faithfully a profile realizes an organ.
2
3use sim_kernel::{ContentId, Coordinate, Expr, HandleId, Ref, Symbol};
4pub(crate) use sim_value::kind::expr_kind;
5
6/// A fidelity badge: how faithfully a subject realizes a named badge, at a
7/// given level, with a reference to the evidence behind it.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct FidelityBadge {
10    /// What the badge is about (typically a profile symbol ref).
11    pub subject: Ref,
12    /// The badge name.
13    pub badge: Symbol,
14    /// Fidelity level, higher meaning more faithful.
15    pub level: u8,
16    /// Reference to the evidence (typically a conformance test) for the badge.
17    pub evidence: Ref,
18}
19
20impl FidelityBadge {
21    /// Construct a badge from its subject, name, level, and evidence.
22    pub fn new(subject: Ref, badge: Symbol, level: u8, evidence: Ref) -> Self {
23        Self {
24            subject,
25            badge,
26            level,
27            evidence,
28        }
29    }
30
31    /// Encode this badge as constructor arguments for the `standard/FidelityBadge` class.
32    pub fn to_constructor_args(&self) -> Vec<Expr> {
33        vec![
34            ref_expr(&self.subject),
35            Expr::Symbol(self.badge.clone()),
36            Expr::String(self.level.to_string()),
37            ref_expr(&self.evidence),
38        ]
39    }
40
41    /// Decode a badge from `standard/FidelityBadge` constructor arguments.
42    pub fn from_constructor_args(args: Vec<Expr>) -> sim_kernel::Result<Self> {
43        let [subject, badge, level, evidence] = args.as_slice() else {
44            return Err(sim_kernel::Error::Eval(
45                "standard/FidelityBadge expects subject, badge, level, evidence".to_owned(),
46            ));
47        };
48        Ok(Self {
49            subject: ref_from_expr(subject)?,
50            badge: symbol_from_expr(badge, "badge")?,
51            level: level_from_expr(level)?,
52            evidence: ref_from_expr(evidence)?,
53        })
54    }
55}
56
57/// Class symbol for the `standard/FidelityBadge` runtime object.
58pub fn fidelity_badge_class_symbol() -> Symbol {
59    Symbol::qualified("standard", "FidelityBadge")
60}
61
62pub(crate) fn ref_expr(reference: &Ref) -> Expr {
63    match reference {
64        Ref::Symbol(symbol) => Expr::Symbol(symbol.clone()),
65        Ref::Content(content) => Expr::List(vec![
66            Expr::Symbol(ref_content_symbol()),
67            Expr::Symbol(content.algorithm.clone()),
68            Expr::Bytes(content.bytes.to_vec()),
69        ]),
70        Ref::Handle(handle) => Expr::List(vec![
71            Expr::Symbol(ref_handle_symbol()),
72            Expr::Bytes(handle.0.to_be_bytes().to_vec()),
73        ]),
74        Ref::Coord(coordinate) => Expr::List(vec![
75            Expr::Symbol(ref_coord_symbol()),
76            Expr::Symbol(coordinate.space.clone()),
77            ref_expr(&Ref::Content(coordinate.ordinal.clone())),
78        ]),
79    }
80}
81
82pub(crate) fn ref_from_expr(expr: &Expr) -> sim_kernel::Result<Ref> {
83    match expr {
84        Expr::Symbol(symbol) => Ok(Ref::Symbol(symbol.clone())),
85        Expr::List(items) => ref_from_list(items),
86        _ => Err(sim_kernel::Error::TypeMismatch {
87            expected: "symbol ref",
88            found: expr_kind(expr),
89        }),
90    }
91}
92
93pub(crate) fn symbol_from_expr(expr: &Expr, expected: &'static str) -> sim_kernel::Result<Symbol> {
94    match expr {
95        Expr::Symbol(symbol) => Ok(symbol.clone()),
96        _ => Err(sim_kernel::Error::TypeMismatch {
97            expected,
98            found: expr_kind(expr),
99        }),
100    }
101}
102
103fn level_from_expr(expr: &Expr) -> sim_kernel::Result<u8> {
104    let Expr::String(level) = expr else {
105        return Err(sim_kernel::Error::TypeMismatch {
106            expected: "level string",
107            found: expr_kind(expr),
108        });
109    };
110    level
111        .parse::<u8>()
112        .map_err(|err| sim_kernel::Error::Eval(format!("invalid fidelity level {level}: {err}")))
113}
114
115fn ref_from_list(items: &[Expr]) -> sim_kernel::Result<Ref> {
116    let Some(Expr::Symbol(head)) = items.first() else {
117        return Err(sim_kernel::Error::TypeMismatch {
118            expected: "ref constructor head",
119            found: items.first().map(expr_kind).unwrap_or("empty list"),
120        });
121    };
122    if head == &ref_content_symbol() {
123        return content_ref_from_items(items);
124    }
125    if head == &ref_handle_symbol() {
126        return handle_ref_from_items(items);
127    }
128    if head == &ref_coord_symbol() {
129        return coord_ref_from_items(items);
130    }
131    Err(sim_kernel::Error::Eval(format!(
132        "unknown standard ref constructor {head}"
133    )))
134}
135
136fn content_ref_from_items(items: &[Expr]) -> sim_kernel::Result<Ref> {
137    let [_, Expr::Symbol(algorithm), Expr::Bytes(bytes)] = items else {
138        return Err(sim_kernel::Error::Eval(
139            "standard/ref-content expects algorithm and 32 bytes".to_owned(),
140        ));
141    };
142    let bytes = bytes_32(bytes)?;
143    Ok(Ref::Content(ContentId::from_bytes(
144        algorithm.clone(),
145        bytes,
146    )))
147}
148
149fn handle_ref_from_items(items: &[Expr]) -> sim_kernel::Result<Ref> {
150    let [_, Expr::Bytes(bytes)] = items else {
151        return Err(sim_kernel::Error::Eval(
152            "standard/ref-handle expects 16 bytes".to_owned(),
153        ));
154    };
155    let bytes: [u8; 16] = bytes.as_slice().try_into().map_err(|_| {
156        sim_kernel::Error::Eval(format!(
157            "handle ref expects 16 bytes, found {}",
158            bytes.len()
159        ))
160    })?;
161    Ok(Ref::Handle(HandleId(u128::from_be_bytes(bytes))))
162}
163
164fn coord_ref_from_items(items: &[Expr]) -> sim_kernel::Result<Ref> {
165    let [_, Expr::Symbol(space), ordinal] = items else {
166        return Err(sim_kernel::Error::Eval(
167            "standard/ref-coord expects space and content ordinal".to_owned(),
168        ));
169    };
170    let Ref::Content(ordinal) = ref_from_expr(ordinal)? else {
171        return Err(sim_kernel::Error::TypeMismatch {
172            expected: "content ordinal ref",
173            found: expr_kind(ordinal),
174        });
175    };
176    Ok(Ref::Coord(Coordinate {
177        space: space.clone(),
178        ordinal,
179    }))
180}
181
182fn bytes_32(bytes: &[u8]) -> sim_kernel::Result<[u8; 32]> {
183    bytes.try_into().map_err(|_| {
184        sim_kernel::Error::Eval(format!(
185            "content ref expects 32 bytes, found {}",
186            bytes.len()
187        ))
188    })
189}
190
191fn ref_content_symbol() -> Symbol {
192    Symbol::qualified("standard", "ref-content")
193}
194
195fn ref_handle_symbol() -> Symbol {
196    Symbol::qualified("standard", "ref-handle")
197}
198
199fn ref_coord_symbol() -> Symbol {
200    Symbol::qualified("standard", "ref-coord")
201}