use crate::misc::bit_vector;
use crate::util::BitVector;
pub struct RhaiAssertEngine {
engine: rhai::Engine,
ast: rhai::AST,
}
impl RhaiAssertEngine {
pub fn new(script_path: &str) -> Self {
let script = std::fs::read_to_string(script_path)
.unwrap_or_else(|e| panic!("Failed to read Rhai script '{script_path}': {e}"));
Self::from_source(script_path, &script)
}
pub fn from_source(name: &str, source: &str) -> Self {
let engine = rhai::Engine::new();
let ast = engine
.compile(source)
.unwrap_or_else(|e| panic!("Failed to compile Rhai script '{name}': {e}"));
Self { engine, ast }
}
pub fn build(stim_filepath: &str, embedded_rhai_script: Option<&str>, logical_assert_filepath: Option<&str>) -> Self {
if let Some(path) = logical_assert_filepath {
return Self::new(path);
}
if let Some(script) = embedded_rhai_script {
return Self::from_source(&format!("embedded in {stim_filepath}"), script);
}
panic!(
"No logical assertion script found. Either embed a #!rhai block \n\
in your Stim circuit file or set `logical_assert_filepath` in the \n\
simulator config.\n\
\n\
Example #!rhai block in a Stim file:\n\
\n\
#!rhai\n\
# fn is_logical_error(shot, readouts, measurements) {{\n\
# readouts[0] != 0\n\
# }}\n\
\n\
Or set in config JSON:\n\
\n\
{{\"logical_assert_filepath\": \"path/to/assert.rhai\"}}"
)
}
pub fn is_logical_error(&self, shot: usize, readouts: Option<&BitVector>, measurements: &BitVector) -> bool {
let ast = &self.ast;
let readouts_arr: rhai::Array = match readouts {
Some(r) => bit_vector::unpack_bits(&r.data, r.size)
.into_iter()
.map(rhai::Dynamic::from)
.collect(),
None => rhai::Array::new(),
};
let measurements_arr: rhai::Array = bit_vector::unpack_bits(&measurements.data, measurements.size)
.into_iter()
.map(rhai::Dynamic::from)
.collect();
let mut scope = rhai::Scope::new();
let result: bool = self
.engine
.call_fn(
&mut scope,
ast,
"is_logical_error",
(shot as rhai::INT, readouts_arr, measurements_arr),
)
.unwrap_or_else(|e| {
panic!(
"Rhai is_logical_error() failed at shot {shot}: {e}\n\
Hint: the function signature should be:\n \
fn is_logical_error(shot, readouts, measurements) {{ ... }}"
)
});
result
}
}
pub fn extract_rhai_script(stim_text: &str) -> Option<String> {
let mut script = String::new();
let mut in_rhai_block = false;
for line in stim_text.lines() {
let trimmed = line.trim();
if trimmed == "#!rhai" {
in_rhai_block = true;
continue;
}
if in_rhai_block {
if let Some(rest) = trimmed.strip_prefix('#') {
let code = rest.strip_prefix(' ').unwrap_or(rest);
script.push_str(code);
script.push('\n');
} else {
in_rhai_block = false;
}
}
}
if script.is_empty() { None } else { Some(script) }
}