1use sim_codec::{Input, decode_with_codec, encode_value_with_codec};
13use sim_cookbook::{CheckResult, RecipeCard, RecipeRun};
14use sim_kernel::{Cx, EncodeOptions, Error, ReadPolicy, Result, Symbol, read_eval_capability};
15
16pub fn require_eval_capability(cx: &Cx) -> Result<()> {
18 if cx.capabilities().contains(&read_eval_capability()) {
19 Ok(())
20 } else {
21 Err(Error::CapabilityDenied {
22 capability: read_eval_capability(),
23 })
24 }
25}
26
27pub fn missing_requires(cx: &Cx, card: &RecipeCard) -> Vec<String> {
33 let loaded: Vec<(String, String)> = cx
34 .registry()
35 .libs()
36 .iter()
37 .map(|lib| {
38 (
39 lib.manifest.id.as_qualified_str(),
40 lib.manifest.id.name.to_string(),
41 )
42 })
43 .collect();
44 card.requires
45 .iter()
46 .filter(|req| {
47 !loaded
48 .iter()
49 .any(|(qualified, name)| qualified == *req || name == *req)
50 })
51 .cloned()
52 .collect()
53}
54
55pub fn run_recipe(cx: &mut Cx, card: &RecipeCard) -> Result<RecipeRun> {
59 let missing = missing_requires(cx, card);
60 if !missing.is_empty() {
61 return Err(Error::Eval(format!(
62 "recipe {} requires libs not loaded: {}",
63 card.id,
64 missing.join(", ")
65 )));
66 }
67
68 let codec = Symbol::qualified("codec", card.codec.as_str());
69 let source = String::from_utf8(card.setup.clone())
70 .map_err(|e| Error::Eval(format!("recipe {} setup is not UTF-8: {e}", card.id)))?;
71 let expr = decode_with_codec(cx, &codec, Input::Text(source), ReadPolicy::default())?;
72
73 let (results, eval_ok) = match cx.eval_expr(expr) {
74 Ok(value) => {
75 let text = encode_value_with_codec(cx, &codec, &value, EncodeOptions::default())?
76 .into_text()?;
77 (vec![text], true)
78 }
79 Err(_) => (Vec::new(), false),
80 };
81
82 let mut checks = Vec::new();
83 let mut all_pass = true;
84 for expectation in &card.expect {
85 let actual = results.get(expectation.form).cloned();
86 let pass = actual.as_deref() == Some(expectation.result.as_str());
87 if !pass {
88 all_pass = false;
89 }
90 checks.push(CheckResult {
91 form: expectation.form,
92 expected: expectation.result.clone(),
93 actual: actual.unwrap_or_else(|| "<no such form>".to_string()),
94 pass,
95 });
96 }
97
98 Ok(RecipeRun {
99 recipe: card.id.clone(),
100 forms: results.len(),
101 results,
102 ok: eval_ok && all_pass,
103 checks,
104 })
105}
106
107pub fn decode_setup(cx: &mut Cx, card: &RecipeCard) -> Result<sim_kernel::Expr> {
109 let codec = Symbol::qualified("codec", card.codec.as_str());
110 let source = String::from_utf8(card.setup.clone())
111 .map_err(|e| Error::Eval(format!("recipe {} setup is not UTF-8: {e}", card.id)))?;
112 decode_with_codec(cx, &codec, Input::Text(source), ReadPolicy::default())
113}