Skip to main content

sim_lib_standard_core/
profile.rs

1//! The `LanguageProfile` model: organ uses, badges, and profile metadata.
2
3use sim_kernel::{CapabilityName, Expr, Ref, Symbol};
4
5use crate::fidelity::{FidelityBadge, expr_kind, symbol_from_expr};
6
7/// One organ a profile uses, with its configuration options.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct OrganUse {
10    /// Symbol of the organ being used.
11    pub organ: Symbol,
12    /// Key/value options configuring the organ.
13    pub options: Vec<(Symbol, Expr)>,
14}
15
16impl OrganUse {
17    /// Use `organ` with no options.
18    pub fn new(organ: Symbol) -> Self {
19        Self {
20            organ,
21            options: Vec::new(),
22        }
23    }
24
25    /// Add an option `key`/`value` pair.
26    pub fn with_option(mut self, key: Symbol, value: Expr) -> Self {
27        self.options.push((key, value));
28        self
29    }
30
31    /// Encode this organ use as an expression (organ symbol plus option map).
32    pub fn to_expr(&self) -> Expr {
33        Expr::List(vec![
34            Expr::Symbol(self.organ.clone()),
35            Expr::Map(
36                self.options
37                    .iter()
38                    .map(|(key, value)| (Expr::Symbol(key.clone()), value.clone()))
39                    .collect(),
40            ),
41        ])
42    }
43
44    /// Decode an organ use from its [`OrganUse::to_expr`] encoding.
45    pub fn from_expr(expr: &Expr) -> sim_kernel::Result<Self> {
46        let Expr::List(items) = expr else {
47            return Err(sim_kernel::Error::TypeMismatch {
48                expected: "organ-use list",
49                found: expr_kind(expr),
50            });
51        };
52        let [organ, options] = items.as_slice() else {
53            return Err(sim_kernel::Error::Eval(
54                "organ use expects organ symbol and option map".to_owned(),
55            ));
56        };
57        let Expr::Map(entries) = options else {
58            return Err(sim_kernel::Error::TypeMismatch {
59                expected: "organ-use option map",
60                found: expr_kind(options),
61            });
62        };
63        Ok(Self {
64            organ: symbol_from_expr(organ, "organ symbol")?,
65            options: entries
66                .iter()
67                .map(|(key, value)| Ok((symbol_from_expr(key, "option symbol")?, value.clone())))
68                .collect::<sim_kernel::Result<Vec<_>>>()?,
69        })
70    }
71}
72
73/// A language profile: the reader, lowering, eval-policy, organs, and metadata
74/// that present one surface language over the shared `Expr` graph.
75///
76/// Profiles are the unit the standard distribution installs, diffs, and tests;
77/// the per-language `sim-lib-lang-*` crates build one each.
78///
79/// # Examples
80///
81/// ```
82/// use sim_kernel::Symbol;
83/// use sim_lib_standard_core::{LanguageProfile, OrganUse, standard_control_organ_symbol};
84///
85/// let profile = LanguageProfile::new(Symbol::qualified("lang", "demo/v1"))
86///     .with_reader(Symbol::qualified("codec", "lisp"))
87///     .with_eval_policy(Symbol::qualified("eval", "default"))
88///     .with_organ(OrganUse::new(standard_control_organ_symbol()));
89///
90/// assert_eq!(profile.reader, Symbol::qualified("codec", "lisp"));
91/// assert_eq!(profile.organs.len(), 1);
92/// ```
93#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct LanguageProfile {
95    /// Symbol naming the profile.
96    pub symbol: Symbol,
97    /// Reader (codec) symbol the profile parses with.
98    pub reader: Symbol,
99    /// Lowering symbol mapping surface forms onto the shared graph.
100    pub lowering: Symbol,
101    /// Eval-policy symbol the profile evaluates under.
102    pub eval_policy: Symbol,
103    /// Organs the profile uses.
104    pub organs: Vec<OrganUse>,
105    /// Optional numeric tower symbol.
106    pub numeric_tower: Option<Symbol>,
107    /// Capabilities the profile requires.
108    pub capabilities: Vec<CapabilityName>,
109    /// Surface forms the profile does not support.
110    pub unsupported_forms: Vec<Symbol>,
111    /// Conformance tests covering the profile.
112    pub conformance_tests: Vec<Symbol>,
113    /// Fidelity badges declared for the profile.
114    pub fidelity_badges: Vec<FidelityBadge>,
115}
116
117impl LanguageProfile {
118    /// Start a profile named `symbol` with unspecified reader/lowering/eval-policy
119    /// and no organs.
120    pub fn new(symbol: Symbol) -> Self {
121        Self {
122            symbol,
123            reader: unspecified_symbol("reader"),
124            lowering: unspecified_symbol("lowering"),
125            eval_policy: unspecified_symbol("eval-policy"),
126            organs: Vec::new(),
127            numeric_tower: None,
128            capabilities: Vec::new(),
129            unsupported_forms: Vec::new(),
130            conformance_tests: Vec::new(),
131            fidelity_badges: Vec::new(),
132        }
133    }
134
135    /// Set the reader symbol.
136    pub fn with_reader(mut self, reader: Symbol) -> Self {
137        self.reader = reader;
138        self
139    }
140
141    /// Set the lowering symbol.
142    pub fn with_lowering(mut self, lowering: Symbol) -> Self {
143        self.lowering = lowering;
144        self
145    }
146
147    /// Set the eval-policy symbol.
148    pub fn with_eval_policy(mut self, eval_policy: Symbol) -> Self {
149        self.eval_policy = eval_policy;
150        self
151    }
152
153    /// Add an organ use.
154    pub fn with_organ(mut self, organ: OrganUse) -> Self {
155        self.organs.push(organ);
156        self
157    }
158
159    /// Set the numeric tower symbol.
160    pub fn with_numeric_tower(mut self, numeric_tower: Symbol) -> Self {
161        self.numeric_tower = Some(numeric_tower);
162        self
163    }
164
165    /// Add a required capability.
166    pub fn requiring(mut self, capability: CapabilityName) -> Self {
167        self.capabilities.push(capability);
168        self
169    }
170
171    /// Add an unsupported surface form.
172    pub fn with_unsupported_form(mut self, form: Symbol) -> Self {
173        self.unsupported_forms.push(form);
174        self
175    }
176
177    /// Add a conformance test.
178    pub fn with_conformance_test(mut self, test: Symbol) -> Self {
179        self.conformance_tests.push(test);
180        self
181    }
182
183    /// Add a fidelity badge.
184    pub fn with_fidelity_badge(mut self, badge: FidelityBadge) -> Self {
185        self.fidelity_badges.push(badge);
186        self
187    }
188
189    /// Encode this profile as constructor arguments for the `standard/Profile` class.
190    pub fn to_constructor_args(&self) -> Vec<Expr> {
191        vec![
192            Expr::Symbol(self.symbol.clone()),
193            Expr::Symbol(self.reader.clone()),
194            Expr::Symbol(self.lowering.clone()),
195            Expr::Symbol(self.eval_policy.clone()),
196            Expr::List(self.organs.iter().map(OrganUse::to_expr).collect()),
197            self.numeric_tower
198                .clone()
199                .map(Expr::Symbol)
200                .unwrap_or(Expr::Nil),
201            Expr::List(
202                self.capabilities
203                    .iter()
204                    .map(|capability| Expr::String(capability.as_str().to_owned()))
205                    .collect(),
206            ),
207            Expr::List(
208                self.unsupported_forms
209                    .iter()
210                    .cloned()
211                    .map(Expr::Symbol)
212                    .collect(),
213            ),
214            Expr::List(
215                self.conformance_tests
216                    .iter()
217                    .cloned()
218                    .map(Expr::Symbol)
219                    .collect(),
220            ),
221            Expr::List(
222                self.fidelity_badges
223                    .iter()
224                    .map(|badge| Expr::Call {
225                        operator: Box::new(Expr::Symbol(crate::fidelity_badge_class_symbol())),
226                        args: badge.to_constructor_args(),
227                    })
228                    .collect(),
229            ),
230        ]
231    }
232
233    /// Decode a profile from `standard/Profile` constructor arguments.
234    pub fn from_constructor_args(args: Vec<Expr>) -> sim_kernel::Result<Self> {
235        let [
236            symbol,
237            reader,
238            lowering,
239            eval_policy,
240            organs,
241            numeric_tower,
242            capabilities,
243            unsupported_forms,
244            conformance_tests,
245            fidelity_badges,
246        ] = args.as_slice()
247        else {
248            return Err(sim_kernel::Error::Eval(
249                "standard/Profile expects ten constructor arguments".to_owned(),
250            ));
251        };
252
253        Ok(Self {
254            symbol: symbol_from_expr(symbol, "profile symbol")?,
255            reader: symbol_from_expr(reader, "reader symbol")?,
256            lowering: symbol_from_expr(lowering, "lowering symbol")?,
257            eval_policy: symbol_from_expr(eval_policy, "eval policy symbol")?,
258            organs: organ_uses_from_expr(organs)?,
259            numeric_tower: optional_symbol(numeric_tower)?,
260            capabilities: capabilities_from_expr(capabilities)?,
261            unsupported_forms: symbols_from_expr(unsupported_forms, "unsupported form")?,
262            conformance_tests: symbols_from_expr(conformance_tests, "conformance test")?,
263            fidelity_badges: badges_from_expr(fidelity_badges)?,
264        })
265    }
266}
267
268/// Class symbol for the `standard/Profile` runtime object.
269pub fn language_profile_class_symbol() -> Symbol {
270    Symbol::qualified("standard", "Profile")
271}
272
273/// Symbol naming the built-in sim-expression profile.
274pub fn sim_expression_profile_symbol() -> Symbol {
275    Symbol::qualified("lang", "sim-expression/v1")
276}
277
278/// The built-in sim-expression profile: the standard distribution's own surface
279/// over the shared `Expr` graph (lisp reader, default eval policy, core organs).
280pub fn sim_expression_profile() -> LanguageProfile {
281    let profile = sim_expression_profile_symbol();
282    let test = Symbol::qualified("test", "sim-expression-core");
283    LanguageProfile::new(profile.clone())
284        .with_reader(Symbol::qualified("codec", "lisp"))
285        .with_lowering(Symbol::qualified("standard", "identity-lowering"))
286        .with_eval_policy(Symbol::qualified("eval", "default"))
287        .with_organ(OrganUse::new(standard_control_organ_symbol()))
288        .with_organ(OrganUse::new(standard_binding_organ_symbol()))
289        .with_organ(OrganUse::new(standard_sequence_organ_symbol()))
290        .with_organ(OrganUse::new(standard_pattern_organ_symbol()))
291        .with_numeric_tower(Symbol::qualified("numbers", "sim-expression"))
292        .with_conformance_test(test.clone())
293        .with_fidelity_badge(FidelityBadge::new(
294            Ref::Symbol(profile),
295            Symbol::qualified("standard", "host-native"),
296            1,
297            Ref::Symbol(test),
298        ))
299}
300
301/// Symbol for the standard control organ.
302pub fn standard_control_organ_symbol() -> Symbol {
303    Symbol::qualified("organ", "control")
304}
305
306/// Symbol for the standard binding organ.
307pub fn standard_binding_organ_symbol() -> Symbol {
308    Symbol::qualified("organ", "binding")
309}
310
311/// Symbol for the standard sequence organ.
312pub fn standard_sequence_organ_symbol() -> Symbol {
313    Symbol::qualified("organ", "sequence")
314}
315
316/// Symbol for the standard pattern organ.
317pub fn standard_pattern_organ_symbol() -> Symbol {
318    Symbol::qualified("organ", "pattern")
319}
320
321fn unspecified_symbol(name: &str) -> Symbol {
322    Symbol::qualified("standard/unspecified", name.to_owned())
323}
324
325fn optional_symbol(expr: &Expr) -> sim_kernel::Result<Option<Symbol>> {
326    match expr {
327        Expr::Nil => Ok(None),
328        Expr::Symbol(symbol) => Ok(Some(symbol.clone())),
329        _ => Err(sim_kernel::Error::TypeMismatch {
330            expected: "optional symbol",
331            found: expr_kind(expr),
332        }),
333    }
334}
335
336fn organ_uses_from_expr(expr: &Expr) -> sim_kernel::Result<Vec<OrganUse>> {
337    let Expr::List(items) = expr else {
338        return Err(sim_kernel::Error::TypeMismatch {
339            expected: "organ-use list",
340            found: expr_kind(expr),
341        });
342    };
343    items.iter().map(OrganUse::from_expr).collect()
344}
345
346fn capabilities_from_expr(expr: &Expr) -> sim_kernel::Result<Vec<CapabilityName>> {
347    let Expr::List(items) = expr else {
348        return Err(sim_kernel::Error::TypeMismatch {
349            expected: "capability list",
350            found: expr_kind(expr),
351        });
352    };
353    items
354        .iter()
355        .map(|item| match item {
356            Expr::String(name) => Ok(CapabilityName::new(name.clone())),
357            _ => Err(sim_kernel::Error::TypeMismatch {
358                expected: "capability string",
359                found: expr_kind(item),
360            }),
361        })
362        .collect()
363}
364
365fn symbols_from_expr(expr: &Expr, expected: &'static str) -> sim_kernel::Result<Vec<Symbol>> {
366    let Expr::List(items) = expr else {
367        return Err(sim_kernel::Error::TypeMismatch {
368            expected: "symbol list",
369            found: expr_kind(expr),
370        });
371    };
372    items
373        .iter()
374        .map(|item| symbol_from_expr(item, expected))
375        .collect()
376}
377
378fn badges_from_expr(expr: &Expr) -> sim_kernel::Result<Vec<FidelityBadge>> {
379    let Expr::List(items) = expr else {
380        return Err(sim_kernel::Error::TypeMismatch {
381            expected: "fidelity badge list",
382            found: expr_kind(expr),
383        });
384    };
385    items
386        .iter()
387        .map(|item| match item {
388            Expr::Call { operator, args } => {
389                let class = symbol_from_expr(operator, "badge class")?;
390                if class != crate::fidelity_badge_class_symbol() {
391                    return Err(sim_kernel::Error::Eval(format!(
392                        "expected standard/FidelityBadge, found {class}"
393                    )));
394                }
395                FidelityBadge::from_constructor_args(args.clone())
396            }
397            _ => Err(sim_kernel::Error::TypeMismatch {
398                expected: "fidelity badge constructor call",
399                found: expr_kind(item),
400            }),
401        })
402        .collect()
403}