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 require_eval_capability(cx)?;
60
61 let missing = missing_requires(cx, card);
62 if !missing.is_empty() {
63 return Err(Error::Eval(format!(
64 "recipe {} requires libs not loaded: {}",
65 card.id,
66 missing.join(", ")
67 )));
68 }
69
70 let codec = Symbol::qualified("codec", card.codec.as_str());
71 let source = String::from_utf8(card.setup.clone())
72 .map_err(|e| Error::Eval(format!("recipe {} setup is not UTF-8: {e}", card.id)))?;
73 let expr = decode_with_codec(cx, &codec, Input::Text(source), ReadPolicy::default())?;
74
75 let (results, eval_ok) = match cx.eval_expr(expr) {
76 Ok(value) => {
77 let text = encode_value_with_codec(cx, &codec, &value, EncodeOptions::default())?
78 .into_text()?;
79 (vec![text], true)
80 }
81 Err(_) => (Vec::new(), false),
82 };
83
84 let mut checks = Vec::new();
85 let mut all_pass = true;
86 for expectation in &card.expect {
87 let actual = results.get(expectation.form).cloned();
88 let pass = actual.as_deref() == Some(expectation.result.as_str());
89 if !pass {
90 all_pass = false;
91 }
92 checks.push(CheckResult {
93 form: expectation.form,
94 expected: expectation.result.clone(),
95 actual: actual.unwrap_or_else(|| "<no such form>".to_string()),
96 pass,
97 });
98 }
99
100 Ok(RecipeRun {
101 recipe: card.id.clone(),
102 forms: results.len(),
103 results,
104 ok: eval_ok && all_pass,
105 checks,
106 })
107}
108
109pub fn decode_setup(cx: &mut Cx, card: &RecipeCard) -> Result<sim_kernel::Expr> {
111 let codec = Symbol::qualified("codec", card.codec.as_str());
112 let source = String::from_utf8(card.setup.clone())
113 .map_err(|e| Error::Eval(format!("recipe {} setup is not UTF-8: {e}", card.id)))?;
114 decode_with_codec(cx, &codec, Input::Text(source), ReadPolicy::default())
115}