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