Files
rhai-python/src/lib.rs

161 lines
5.0 KiB
Rust

use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use pyo3::IntoPyObjectExt;
use rhai::{Array, Dynamic, Engine, Map, Scope};
/// Convert Rhai Dynamic to Python object
fn dynamic_to_python(py: Python<'_>, value: Dynamic) -> PyResult<PyObject> {
match value.type_name() {
"i64" => Ok(value.as_int().unwrap().into_py_any(py).unwrap()),
"f64" => Ok(value.as_float().unwrap().into_py_any(py).unwrap()),
"bool" => Ok(value.as_bool().unwrap().into_py_any(py).unwrap()),
"string" => Ok(value.into_string().unwrap().into_py_any(py).unwrap()),
"char" => Ok(value
.as_char()
.unwrap()
.to_string()
.into_py_any(py)
.unwrap()),
"()" => Ok(py.None()),
"array" => {
let array = value.cast::<Array>();
let py_list = PyList::empty(py);
for item in array {
py_list.append(dynamic_to_python(py, item)?)?;
}
Ok(py_list.into_py_any(py)?.into())
}
"map" => {
let map = value.cast::<Map>();
let py_dict = PyDict::new(py);
for (key, value) in map {
let py_key = key.to_string().into_py_any(py).unwrap();
let py_value = dynamic_to_python(py, value)?;
py_dict.set_item(py_key, py_value)?;
}
Ok(py_dict.into_py_any(py)?.into())
}
_ => Err(PyRuntimeError::new_err(format!(
"Unsupported Rhai type: {}",
value.type_name()
))),
}
}
/// Convert Python object to Rhai Dynamic
fn python_to_dynamic(py: Python<'_>, obj: &Bound<'_, PyAny>) -> PyResult<Dynamic> {
if obj.is_none() {
Ok(Dynamic::UNIT)
} else if let Ok(val) = obj.extract::<bool>() {
Ok(Dynamic::from(val))
} else if let Ok(val) = obj.extract::<i64>() {
Ok(Dynamic::from(val))
} else if let Ok(val) = obj.extract::<f64>() {
Ok(Dynamic::from(val))
} else if let Ok(val) = obj.extract::<String>() {
Ok(Dynamic::from(val))
} else if let Ok(py_list) = obj.downcast::<PyList>() {
let mut array = Array::new();
for item in py_list {
array.push(python_to_dynamic(py, &item)?);
}
Ok(Dynamic::from(array))
} else if let Ok(py_dict) = obj.downcast::<PyDict>() {
let mut map = Map::new();
for (key, value) in py_dict {
let key_str = key.extract::<String>()?;
let rhai_value = python_to_dynamic(py, &value)?;
map.insert(key_str.into(), rhai_value);
}
Ok(Dynamic::from(map))
} else {
Err(PyRuntimeError::new_err(format!(
"Unsupported Python type: {}",
obj.get_type().name()?
)))
}
}
#[pyclass]
struct RhaiEngine {
engine: Engine,
scope: Scope<'static>,
}
#[pymethods]
impl RhaiEngine {
#[new]
fn new() -> Self {
Self {
engine: Engine::new(),
scope: Scope::new(),
}
}
/// Evaluate a Rhai script and return the result
fn eval(&mut self, py: Python<'_>, script: &str) -> PyResult<PyObject> {
match self
.engine
.eval_with_scope::<Dynamic>(&mut self.scope, script)
{
Ok(result) => dynamic_to_python(py, result),
Err(err) => Err(PyRuntimeError::new_err(format!("Rhai error: {}", err))),
}
}
/// Set a variable in the engine scope
fn set_var(&mut self, py: Python<'_>, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> {
let dynamic_value = python_to_dynamic(py, value)?;
self.scope.set_value(name, dynamic_value);
Ok(())
}
/// Get a variable from the engine scope
fn get_var(&self, py: Python<'_>, name: &str) -> PyResult<PyObject> {
match self.scope.get_value::<Dynamic>(name) {
Some(value) => dynamic_to_python(py, value),
None => Err(PyRuntimeError::new_err(format!(
"Variable '{}' not found",
name
))),
}
}
/// Check if a variable exists in the scope
fn has_var(&self, name: &str) -> bool {
self.scope.contains(name)
}
/// Clear all variables from the scope
fn clear_scope(&mut self) {
self.scope.clear();
}
/// Compile a script and return whether it's valid
fn compile(&self, script: &str) -> PyResult<bool> {
match self.engine.compile(script) {
Ok(_) => Ok(true),
Err(err) => Err(PyRuntimeError::new_err(format!(
"Compilation error: {}",
err
))),
}
}
/// Get the list of all variable names in the scope
fn list_vars(&self) -> Vec<String> {
self.scope
.iter()
.map(|(name, _, _)| name.to_string())
.collect()
}
}
/// A Python module implemented in Rust using PyO3
#[pymodule]
fn rhai_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<RhaiEngine>()?;
Ok(())
}