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