initial commit
https://claude.ai/chat/39b41476-837c-4d19-b91f-6b6cd6ce9629
This commit is contained in:
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "rhai-python"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "rhai_python"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
rhai = { version = "1.17", features = ["sync"] }
|
||||
pyo3 = { version = "0.20", features = ["extension-module"] }
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = "0.20"
|
||||
119
README.md
Normal file
119
README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Rhai Python Bindings - Setup Instructions
|
||||
|
||||
This is a basic implementation of Python bindings for the Rhai scripting language using PyO3.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Rust** (latest stable version)
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
2. **Python** 3.8+ with development headers
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install python3-dev
|
||||
|
||||
# macOS (with Homebrew)
|
||||
brew install python
|
||||
|
||||
# Windows: Install Python from python.org
|
||||
```
|
||||
|
||||
3. **Maturin** (for building Python extensions)
|
||||
```bash
|
||||
pip install maturin
|
||||
```
|
||||
|
||||
## Project Setup
|
||||
|
||||
1. Create the project directory structure:
|
||||
```bash
|
||||
mkdir rhai-python
|
||||
cd rhai-python
|
||||
mkdir src python/rhai
|
||||
```
|
||||
|
||||
2. Copy the provided files to their respective locations:
|
||||
- `Cargo.toml` → project root
|
||||
- `pyproject.toml` → project root
|
||||
- `src/lib.rs` → src directory
|
||||
- `python/rhai/__init__.py` → python/rhai directory
|
||||
- `example_usage.py` → project root (for testing)
|
||||
|
||||
## Building
|
||||
|
||||
1. **Development build** (recommended for testing):
|
||||
```bash
|
||||
maturin develop
|
||||
```
|
||||
This builds the extension and installs it in your current Python environment.
|
||||
|
||||
2. **Release build**:
|
||||
```bash
|
||||
maturin build --release
|
||||
```
|
||||
|
||||
3. **Build wheel for distribution**:
|
||||
```bash
|
||||
maturin build --release --out dist/
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
After building with `maturin develop`, you can test the bindings:
|
||||
|
||||
```bash
|
||||
python example_usage.py
|
||||
```
|
||||
|
||||
Or interactively:
|
||||
```python
|
||||
import rhai
|
||||
|
||||
# Quick evaluation
|
||||
result = rhai.eval("40 + 2")
|
||||
print(result) # 42
|
||||
|
||||
# Using the engine
|
||||
engine = rhai.RhaiEngine()
|
||||
engine.set_var("x", 10)
|
||||
result = engine.eval("x * 2")
|
||||
print(result) # 20
|
||||
```
|
||||
|
||||
## Current Features
|
||||
|
||||
- ✅ Basic script evaluation
|
||||
- ✅ Variable binding (Python → Rhai)
|
||||
- ✅ Variable retrieval (Rhai → Python)
|
||||
- ✅ Type conversion for:
|
||||
- Integers, floats, booleans, strings
|
||||
- Arrays (lists)
|
||||
- Objects (dictionaries)
|
||||
- ✅ Error handling
|
||||
- ✅ Script compilation checking
|
||||
- ✅ Scope management
|
||||
|
||||
## Limitations & TODOs
|
||||
|
||||
This is a basic implementation. Future enhancements could include:
|
||||
|
||||
- [ ] Function registration (calling Python functions from Rhai)
|
||||
- [ ] Custom type support
|
||||
- [ ] Module system integration
|
||||
- [ ] AST manipulation
|
||||
- [ ] Debugging support
|
||||
- [ ] Threading/async support
|
||||
- [ ] Performance optimizations
|
||||
- [ ] More comprehensive error types
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Import errors**: Make sure you've run `maturin develop` after making changes.
|
||||
|
||||
**Build errors**: Ensure you have the correct Python development headers installed.
|
||||
|
||||
**Type conversion errors**: The current implementation supports basic types. Complex Python objects will raise conversion errors.
|
||||
|
||||
**Performance**: This is a proof-of-concept implementation. Production use would benefit from optimization.
|
||||
124
example_usage.py
Normal file
124
example_usage.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example usage of the Rhai Python bindings
|
||||
"""
|
||||
|
||||
import rhai
|
||||
|
||||
|
||||
def main():
|
||||
print("=== Basic Rhai Python Bindings Demo ===\n")
|
||||
|
||||
# Quick evaluation
|
||||
print("1. Quick evaluation:")
|
||||
result = rhai.eval("40 + 2")
|
||||
print(f" rhai.eval('40 + 2') = {result}")
|
||||
print()
|
||||
|
||||
# Using the engine directly
|
||||
print("2. Using RhaiEngine:")
|
||||
engine = rhai.RhaiEngine()
|
||||
|
||||
# Basic arithmetic
|
||||
result = engine.eval("10 * 5 + 3")
|
||||
print(f" 10 * 5 + 3 = {result}")
|
||||
|
||||
# String operations
|
||||
result = engine.eval('"Hello, " + "World!"')
|
||||
print(f" String concatenation: {result}")
|
||||
|
||||
# Boolean operations
|
||||
result = engine.eval("true && false")
|
||||
print(f" Boolean operation: {result}")
|
||||
print()
|
||||
|
||||
# Variables
|
||||
print("3. Variable operations:")
|
||||
engine.set_var("x", 42)
|
||||
engine.set_var("name", "Rhai")
|
||||
|
||||
result = engine.eval("x * 2")
|
||||
print(f" x = 42, x * 2 = {result}")
|
||||
|
||||
result = engine.eval('"Hello, " + name + "!"')
|
||||
print(f" name = 'Rhai', greeting = {result}")
|
||||
|
||||
# Check variables
|
||||
print(f" Variables in scope: {engine.list_vars()}")
|
||||
print(f" Has variable 'x': {engine.has_var('x')}")
|
||||
print(f" Get variable 'x': {engine.get_var('x')}")
|
||||
print()
|
||||
|
||||
# Arrays
|
||||
print("4. Array operations:")
|
||||
result = engine.eval("[1, 2, 3, 4, 5]")
|
||||
print(f" Array creation: {result}")
|
||||
|
||||
engine.set_var("arr", [10, 20, 30])
|
||||
result = engine.eval("arr[1]")
|
||||
print(f" Array indexing arr[1]: {result}")
|
||||
|
||||
result = engine.eval("arr.len()")
|
||||
print(f" Array length: {result}")
|
||||
print()
|
||||
|
||||
# Objects/Maps
|
||||
print("5. Object/Map operations:")
|
||||
engine.set_var("obj", {"name": "test", "value": 123})
|
||||
result = engine.eval("obj.name")
|
||||
print(f" Object property access: {result}")
|
||||
|
||||
result = engine.eval('#{a: 1, b: "hello", c: true}')
|
||||
print(f" Object creation: {result}")
|
||||
print()
|
||||
|
||||
# Control flow
|
||||
print("6. Control flow:")
|
||||
result = engine.eval("""
|
||||
let x = 10;
|
||||
if x > 5 {
|
||||
"x is greater than 5"
|
||||
} else {
|
||||
"x is not greater than 5"
|
||||
}
|
||||
""")
|
||||
print(f" If statement result: {result}")
|
||||
|
||||
result = engine.eval("""
|
||||
let sum = 0;
|
||||
for i in 1..6 {
|
||||
sum += i;
|
||||
}
|
||||
sum
|
||||
""")
|
||||
print(f" Loop sum 1-5: {result}")
|
||||
print()
|
||||
|
||||
# Functions
|
||||
print("7. Functions:")
|
||||
result = engine.eval("""
|
||||
fn double(x) {
|
||||
x * 2
|
||||
}
|
||||
double(21)
|
||||
""")
|
||||
print(f" Custom function result: {result}")
|
||||
print()
|
||||
|
||||
# Error handling
|
||||
print("8. Error handling:")
|
||||
try:
|
||||
engine.eval("undefined_variable")
|
||||
except RuntimeError as e:
|
||||
print(f" Caught error: {e}")
|
||||
|
||||
try:
|
||||
engine.compile("invalid syntax {{")
|
||||
except RuntimeError as e:
|
||||
print(f" Compilation error: {e}")
|
||||
|
||||
print("\n=== Demo Complete ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
pyproject.toml
Normal file
29
pyproject.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "rhai-python"
|
||||
description = "Python bindings for the Rhai scripting language"
|
||||
authors = [
|
||||
{name = "Your Name", email = "your.email@example.com"}
|
||||
]
|
||||
license = {text = "MIT"}
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Rust",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[tool.maturin]
|
||||
features = ["pyo3/extension-module"]
|
||||
module-name = "rhai_python"
|
||||
29
python/rhai/__init__.py
Normal file
29
python/rhai/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Rhai Python Bindings
|
||||
|
||||
Python bindings for the Rhai scripting language.
|
||||
"""
|
||||
|
||||
from .rhai_python import RhaiEngine, __version__
|
||||
|
||||
__all__ = ["RhaiEngine", "__version__"]
|
||||
|
||||
|
||||
# Convenience function for quick evaluation
|
||||
def eval(script: str):
|
||||
"""
|
||||
Evaluate a Rhai script and return the result.
|
||||
|
||||
Args:
|
||||
script: The Rhai script to evaluate
|
||||
|
||||
Returns:
|
||||
The result of the script evaluation
|
||||
|
||||
Example:
|
||||
>>> import rhai
|
||||
>>> rhai.eval("40 + 2")
|
||||
42
|
||||
"""
|
||||
engine = RhaiEngine()
|
||||
return engine.eval(script)
|
||||
156
src/lib.rs
Normal file
156
src/lib.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use pyo3::exceptions::PyRuntimeError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyList};
|
||||
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map, Scope};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 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().to_object(py)),
|
||||
"f64" => Ok(value.as_float().unwrap().to_object(py)),
|
||||
"bool" => Ok(value.as_bool().unwrap().to_object(py)),
|
||||
"string" => Ok(value.into_string().unwrap().to_object(py)),
|
||||
"char" => Ok(value.as_char().unwrap().to_string().to_object(py)),
|
||||
"()" => 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.to_object(py))
|
||||
}
|
||||
"map" => {
|
||||
let map = value.cast::<Map>();
|
||||
let py_dict = PyDict::new(py);
|
||||
for (key, value) in map {
|
||||
let py_key = key.to_object(py);
|
||||
let py_value = dynamic_to_python(py, value)?;
|
||||
py_dict.set_item(py_key, py_value)?;
|
||||
}
|
||||
Ok(py_dict.to_object(py))
|
||||
}
|
||||
_ => Err(PyRuntimeError::new_err(format!(
|
||||
"Unsupported Rhai type: {}",
|
||||
value.type_name()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Python object to Rhai Dynamic
|
||||
fn python_to_dynamic(py: Python<'_>, obj: &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: &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(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<RhaiEngine>()?;
|
||||
m.add("__version__", "0.1.0")?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user