1use std::sync::Arc;
11
12use sim_kernel::{
13 AbiVersion, Cx, Export, Lib, LibId, LibManifest, LibTarget, Linker, Ref, Result, Symbol,
14 Version,
15};
16
17use crate::{
18 FidelityBadge, LanguageProfile, LanguageProfileValue, ProfileRegistry,
19 publish_profile_claims_for_lib,
20};
21
22pub type ProfileOrganPublisher = fn(&mut Cx, LibId) -> Result<()>;
24
25type ProfileBackingInstaller = fn(&mut Cx) -> Result<()>;
26
27#[derive(Clone)]
29pub struct ProfileBackingLib {
30 organ: Symbol,
31 manifest: Symbol,
32 install: Option<ProfileBackingInstaller>,
33 publish_claims: Option<ProfileOrganPublisher>,
34}
35
36impl ProfileBackingLib {
37 pub fn loadable(
39 organ: Symbol,
40 manifest: Symbol,
41 install: ProfileBackingInstaller,
42 publish_claims: Option<ProfileOrganPublisher>,
43 ) -> Self {
44 Self {
45 organ,
46 manifest,
47 install: Some(install),
48 publish_claims,
49 }
50 }
51
52 pub fn unresolved(organ: Symbol, manifest: Symbol) -> Self {
55 Self {
56 organ,
57 manifest,
58 install: None,
59 publish_claims: None,
60 }
61 }
62}
63
64pub fn fidelity_badge(profile: &Symbol, badge: Symbol, level: u8, test: &Symbol) -> FidelityBadge {
69 FidelityBadge::new(
70 Ref::Symbol(profile.clone()),
71 badge,
72 level,
73 Ref::Symbol(test.clone()),
74 )
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct FidelityBadgeSpec {
82 pub badge: Symbol,
84 pub level: u8,
86 pub test: Symbol,
88}
89
90impl FidelityBadgeSpec {
91 pub fn into_badge(self, profile: &Symbol) -> FidelityBadge {
93 fidelity_badge(profile, self.badge, self.level, &self.test)
94 }
95}
96
97pub fn install_language_profile(
106 cx: &mut Cx,
107 registry: &mut ProfileRegistry,
108 profile: LanguageProfile,
109 backing_libs: &[ProfileBackingLib],
110 extra_publishers: &[ProfileOrganPublisher],
111) -> Result<LanguageProfile> {
112 let profile = resolve_profile_backings(cx, profile, backing_libs)?;
113 let lib_id = ensure_profile_lib(cx, &profile)?;
114 registry.register_profile(profile.clone())?;
115 publish_profile_claims_for_lib(cx, lib_id, &profile)?;
116 for publish in extra_publishers {
117 publish(cx, lib_id)?;
118 }
119 Ok(profile)
120}
121
122pub fn language_profile_lib_symbol(profile: &Symbol) -> Symbol {
124 Symbol::qualified("standard/profile", profile.to_string())
125}
126
127#[derive(Clone)]
128struct LanguageProfileLib {
129 profile: LanguageProfile,
130}
131
132impl LanguageProfileLib {
133 fn new(profile: LanguageProfile) -> Self {
134 Self { profile }
135 }
136}
137
138impl Lib for LanguageProfileLib {
139 fn manifest(&self) -> LibManifest {
140 LibManifest {
141 id: language_profile_lib_symbol(&self.profile.symbol),
142 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
143 abi: AbiVersion { major: 0, minor: 1 },
144 target: LibTarget::HostRegistered,
145 requires: Vec::new(),
146 capabilities: Vec::new(),
147 exports: vec![Export::Value {
148 symbol: self.profile.symbol.clone(),
149 }],
150 }
151 }
152
153 fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
154 linker.value(
155 self.profile.symbol.clone(),
156 cx.factory()
157 .opaque(Arc::new(LanguageProfileValue::new(self.profile.clone())))?,
158 )
159 }
160}
161
162fn ensure_profile_lib(cx: &mut Cx, profile: &LanguageProfile) -> Result<LibId> {
163 let lib = LanguageProfileLib::new(profile.clone());
164 let manifest = lib.manifest();
165 if let Some(loaded) = cx.registry().lib(&manifest.id) {
166 return Ok(loaded.id);
167 }
168 cx.load_lib(&lib)
169}
170
171fn resolve_profile_backings(
172 cx: &mut Cx,
173 profile: LanguageProfile,
174 backing_libs: &[ProfileBackingLib],
175) -> Result<LanguageProfile> {
176 let mut declared = std::collections::BTreeMap::new();
177 for backing in backing_libs {
178 let replaced = declared.insert(backing.organ.clone(), backing.clone());
179 if replaced.is_some() {
180 return Err(sim_kernel::Error::Eval(format!(
181 "duplicate backing library spec for organ {}",
182 backing.organ
183 )));
184 }
185 }
186
187 let requested_organs = profile.organs.clone();
188 let mut resolved = profile;
189 resolved.organs.clear();
190 resolved.backing_requirements.clear();
191
192 for organ in requested_organs {
193 let backing = declared.remove(&organ.organ).unwrap_or_else(|| {
194 ProfileBackingLib::unresolved(
195 organ.organ.clone(),
196 default_backing_manifest_for_organ(&organ.organ),
197 )
198 });
199 if let Some(loaded) = resolve_loaded_backing(cx, &backing)? {
200 resolved.organs.push(organ);
201 if let Some(publish_claims) = backing.publish_claims {
202 publish_claims(cx, loaded.id)?;
203 }
204 } else {
205 resolved = resolved.with_backing_requirement(backing.manifest);
206 }
207 }
208
209 if let Some(unused) = declared.into_values().next() {
210 return Err(sim_kernel::Error::Eval(format!(
211 "backing library spec for {} does not match any declared organ",
212 unused.organ
213 )));
214 }
215
216 Ok(resolved)
217}
218
219fn resolve_loaded_backing(
220 cx: &mut Cx,
221 backing: &ProfileBackingLib,
222) -> Result<Option<sim_kernel::LoadedLib>> {
223 let Some(install) = backing.install else {
224 return Ok(None);
225 };
226 install(cx)?;
227 let loaded = cx
228 .registry()
229 .lib(&backing.manifest)
230 .cloned()
231 .ok_or_else(|| {
232 sim_kernel::Error::Eval(format!(
233 "backing library {} for organ {} did not load",
234 backing.manifest, backing.organ
235 ))
236 })?;
237 if loaded.exports.is_empty() {
238 return Err(sim_kernel::Error::Eval(format!(
239 "backing library {} for organ {} published no live exports",
240 backing.manifest, backing.organ
241 )));
242 }
243 Ok(Some(loaded))
244}
245
246fn default_backing_manifest_for_organ(organ: &Symbol) -> Symbol {
247 Symbol::qualified("sim", organ.name.clone())
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use std::cell::RefCell;
254
255 fn profile_symbol() -> Symbol {
256 Symbol::qualified("test", "profile")
257 }
258
259 fn test_symbol() -> Symbol {
260 Symbol::qualified("test", "conformance")
261 }
262
263 fn badge_symbol() -> Symbol {
264 Symbol::qualified("test", "badge")
265 }
266
267 #[test]
268 fn fidelity_badge_matches_manual_construction() {
269 let profile = profile_symbol();
270 let test = test_symbol();
271 let helper = fidelity_badge(&profile, badge_symbol(), 1, &test);
272 let manual = FidelityBadge::new(
273 Ref::Symbol(profile.clone()),
274 badge_symbol(),
275 1,
276 Ref::Symbol(test.clone()),
277 );
278 assert_eq!(helper, manual);
279 }
280
281 #[test]
282 fn spec_into_badge_matches_fidelity_badge() {
283 let profile = profile_symbol();
284 let spec = FidelityBadgeSpec {
285 badge: badge_symbol(),
286 level: 2,
287 test: test_symbol(),
288 };
289 let from_spec = spec.into_badge(&profile);
290 let direct = fidelity_badge(&profile, badge_symbol(), 2, &test_symbol());
291 assert_eq!(from_spec, direct);
292 }
293
294 #[test]
295 fn install_language_profile_registers_and_runs_publishers_in_order() {
296 use std::sync::Arc;
297
298 use sim_kernel::{
299 AbiVersion, DefaultFactory, Export, Lib, LibManifest, LibTarget, LoadCx,
300 NoopEvalPolicy, Version,
301 };
302
303 thread_local! {
304 static CALLS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
305 }
306
307 struct FixtureLib {
308 manifest: Symbol,
309 export: Symbol,
310 }
311
312 impl Lib for FixtureLib {
313 fn manifest(&self) -> LibManifest {
314 LibManifest {
315 id: self.manifest.clone(),
316 version: Version("0.1.0".to_owned()),
317 abi: AbiVersion { major: 0, minor: 1 },
318 target: LibTarget::HostRegistered,
319 requires: Vec::new(),
320 capabilities: Vec::new(),
321 exports: vec![Export::Value {
322 symbol: self.export.clone(),
323 }],
324 }
325 }
326
327 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
328 linker.value(self.export.clone(), cx.factory().bool(true)?)?;
329 Ok(())
330 }
331 }
332
333 fn install_backing_one(cx: &mut Cx) -> Result<()> {
334 let manifest = Symbol::qualified("sim", "organ-one");
335 if cx.registry().lib(&manifest).is_none() {
336 cx.load_lib(&FixtureLib {
337 manifest: manifest.clone(),
338 export: Symbol::qualified("test", "backing-one"),
339 })?;
340 }
341 Ok(())
342 }
343
344 fn install_backing_two(cx: &mut Cx) -> Result<()> {
345 let manifest = Symbol::qualified("sim", "organ-two");
346 if cx.registry().lib(&manifest).is_none() {
347 cx.load_lib(&FixtureLib {
348 manifest: manifest.clone(),
349 export: Symbol::qualified("test", "backing-two"),
350 })?;
351 }
352 Ok(())
353 }
354
355 fn first(_cx: &mut Cx, _lib_id: LibId) -> Result<()> {
356 CALLS.with(|calls| calls.borrow_mut().push(1));
357 Ok(())
358 }
359
360 fn second(_cx: &mut Cx, _lib_id: LibId) -> Result<()> {
361 CALLS.with(|calls| calls.borrow_mut().push(2));
362 Ok(())
363 }
364
365 CALLS.with(|calls| calls.borrow_mut().clear());
366
367 let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
368 let mut registry = ProfileRegistry::new();
369 let profile = LanguageProfile::new(profile_symbol())
370 .with_organ(crate::OrganUse::new(Symbol::qualified("organ", "one")))
371 .with_organ(crate::OrganUse::new(Symbol::qualified("organ", "two")));
372
373 let installed = install_language_profile(
374 &mut cx,
375 &mut registry,
376 profile.clone(),
377 &[
378 ProfileBackingLib::loadable(
379 Symbol::qualified("organ", "one"),
380 Symbol::qualified("sim", "organ-one"),
381 install_backing_one,
382 Some(first),
383 ),
384 ProfileBackingLib::loadable(
385 Symbol::qualified("organ", "two"),
386 Symbol::qualified("sim", "organ-two"),
387 install_backing_two,
388 Some(second),
389 ),
390 ],
391 &[],
392 )
393 .expect("install language profile");
394
395 assert_eq!(installed, profile);
396 assert!(registry.profile(&profile_symbol()).is_some());
397 CALLS.with(|calls| assert_eq!(*calls.borrow(), vec![1, 2]));
398 }
399
400 #[test]
401 fn install_language_profile_records_unresolved_backing_requirements() {
402 use std::sync::Arc;
403
404 use sim_kernel::{
405 ClaimPattern, DefaultFactory, NoopEvalPolicy, Ref, card::card_kind_predicate,
406 standard::standard_organ_predicate, standard::standard_profile_kind,
407 };
408
409 let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
410 let mut registry = ProfileRegistry::new();
411 let organ = Symbol::qualified("organ", "missing");
412 let profile =
413 LanguageProfile::new(profile_symbol()).with_organ(crate::OrganUse::new(organ.clone()));
414
415 let installed = install_language_profile(&mut cx, &mut registry, profile, &[], &[])
416 .expect("install language profile");
417
418 assert!(installed.organs.is_empty());
419 assert_eq!(
420 installed.backing_requirements,
421 vec![Symbol::qualified("sim", "missing")]
422 );
423 assert_eq!(
424 registry
425 .profile(&profile_symbol())
426 .unwrap()
427 .backing_requirements,
428 vec![Symbol::qualified("sim", "missing")]
429 );
430 assert_eq!(
431 cx.query_facts(ClaimPattern::exact(
432 Ref::Symbol(profile_symbol()),
433 card_kind_predicate(),
434 Ref::Symbol(standard_profile_kind()),
435 ))
436 .unwrap()
437 .len(),
438 1
439 );
440 assert!(
441 cx.query_facts(ClaimPattern::exact(
442 Ref::Symbol(profile_symbol()),
443 standard_organ_predicate(),
444 Ref::Symbol(organ),
445 ))
446 .unwrap()
447 .is_empty()
448 );
449 }
450}