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