move rhailib to herolib
This commit is contained in:
11
rhailib/src/derive/Cargo.toml
Normal file
11
rhailib/src/derive/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "derive"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
quote = "1.0"
|
78
rhailib/src/derive/README.md
Normal file
78
rhailib/src/derive/README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Rhai Derive Macros
|
||||
|
||||
This crate provides procedural macros to simplify the integration of Rust types with the Rhai scripting engine.
|
||||
|
||||
## `RhaiApi` Derive Macro
|
||||
|
||||
The `RhaiApi` macro automatically generates a Rhai module with a fluent, builder-style API for your Rust structs. This allows you to create and modify your structs in Rhai scripts using chained method calls.
|
||||
|
||||
### How It Works
|
||||
|
||||
When you derive `RhaiApi` on a struct, the macro generates:
|
||||
|
||||
1. A new Rust module named `{struct_name}_rhai_dsl`.
|
||||
2. A Rhai `export_module` within that module named `generated_rhai_module`.
|
||||
3. A `new_{struct_name}()` function to create a new instance of your struct.
|
||||
4. Setter functions for each field in your struct, allowing for method chaining.
|
||||
5. An `id()` function to retrieve the object's ID.
|
||||
|
||||
### Example
|
||||
|
||||
**Rust Struct Definition:**
|
||||
|
||||
```rust
|
||||
use derive::RhaiApi;
|
||||
|
||||
#[derive(RhaiApi, Clone)]
|
||||
pub struct Product {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub price: f64,
|
||||
}
|
||||
```
|
||||
|
||||
**Generated Rhai API Usage:**
|
||||
|
||||
```rhai
|
||||
// Import the generated module
|
||||
import product_rhai_dsl::generated_rhai_module as product_api;
|
||||
|
||||
// Use the fluent API to build a new product
|
||||
let my_product = product_api::new_product()
|
||||
.id(1)
|
||||
.name("Awesome Gadget")
|
||||
.price(99.99);
|
||||
|
||||
print(my_product.id()); // prints 1
|
||||
```
|
||||
|
||||
## `FromVec` Derive Macro
|
||||
|
||||
The `FromVec` macro is a utility for tuple structs that contain a single field. It implements the `From<T>` trait, where `T` is the inner type, allowing for seamless conversions.
|
||||
|
||||
### Example
|
||||
|
||||
**Rust Struct Definition:**
|
||||
|
||||
```rust
|
||||
use derive::FromVec;
|
||||
|
||||
#[derive(FromVec)]
|
||||
pub struct MyVec(Vec<u8>);
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```rust
|
||||
let data = vec![1, 2, 3];
|
||||
let my_vec = MyVec::from(data);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To use these macros in your project, add this crate as a dependency in your `Cargo.toml` file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
derive = { path = "../path/to/rhailib/src/derive" }
|
||||
```
|
67
rhailib/src/derive/docs/ARCHITECTURE.md
Normal file
67
rhailib/src/derive/docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Architecture of the `derive` Crate
|
||||
|
||||
The `derive` crate is a procedural macro crate responsible for generating boilerplate code that integrates Rust structs with the Rhai scripting engine. It simplifies the process of exposing Rust types and their properties to Rhai scripts.
|
||||
|
||||
## Core Functionality
|
||||
|
||||
The crate provides two main procedural macros:
|
||||
|
||||
1. `#[derive(RhaiApi)]`
|
||||
2. `#[derive(FromVec)]`
|
||||
|
||||
---
|
||||
|
||||
## `#[derive(RhaiApi)]`
|
||||
|
||||
This is the primary macro of the crate. When applied to a Rust struct, it automatically generates a Rhai-compatible DSL (Domain-Specific Language) for that struct.
|
||||
|
||||
### Generated Code Structure
|
||||
|
||||
For a struct named `MyStruct`, the macro generates a new module named `my_struct_rhai_dsl`. This module contains a Rhai `export_module` with the following functions:
|
||||
|
||||
* **`new_my_struct()`**: A constructor function that creates a new instance of `MyStruct` within the Rhai engine.
|
||||
* **Setter Functions**: For each field in `MyStruct`, a corresponding setter function is generated. For a field named `my_field`, a Rhai function `my_field(value)` is created to set its value.
|
||||
* **`id()`**: A function to retrieve the ID of the object.
|
||||
|
||||
This allows for a fluent, chainable API within Rhai scripts, like so:
|
||||
|
||||
```rhai
|
||||
let my_object = new_my_struct().field1(42).field2("hello");
|
||||
```
|
||||
|
||||
### Implementation Details
|
||||
|
||||
The implementation resides in `src/rhai_api.rs`. It uses the `syn` crate to parse the input `DeriveInput` and the `quote` crate to construct the output `TokenStream`.
|
||||
|
||||
The process is as follows:
|
||||
|
||||
1. The macro input is parsed into a `DeriveInput` AST (Abstract Syntax Tree).
|
||||
2. The struct's name and fields are extracted from the AST.
|
||||
3. A new module name is generated based on the struct's name (e.g., `MyStruct` -> `my_struct_rhai_dsl`).
|
||||
4. Using the `quote!` macro, the code for the new module, the `export_module`, the constructor, and the setter functions is generated.
|
||||
5. The generated code is returned as a `TokenStream`, which the compiler then incorporates into the crate.
|
||||
|
||||
### Architectural Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Rust Struct Definition] -- `#[derive(RhaiApi)]` --> B{`derive` Crate};
|
||||
B -- `syn` --> C[Parse Struct AST];
|
||||
C -- Extract Fields & Name --> D[Generate Code with `quote`];
|
||||
D -- Create --> E[Constructor `new_...()`];
|
||||
D -- Create --> F[Setter Functions `field(...)`];
|
||||
D -- Create --> G[`id()` function];
|
||||
E & F & G -- Packaged into --> H[Rhai `export_module`];
|
||||
H -- Returned as `TokenStream` --> I[Compiler];
|
||||
I -- Integrates into --> J[Final Binary];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `#[derive(FromVec)]`
|
||||
|
||||
This is a simpler utility macro. Its purpose is to generate a `From<Vec<T>>` implementation for a tuple struct that contains a single `Vec<T>`. This is useful for converting a vector of items into a specific newtype-pattern struct.
|
||||
|
||||
### Implementation
|
||||
|
||||
The implementation is located directly in `src/lib.rs`. It parses the input struct and, if it's a single-element tuple struct, generates the corresponding `From` implementation.
|
117
rhailib/src/derive/src/lib.rs
Normal file
117
rhailib/src/derive/src/lib.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
//! # Derive Macros for Rhai Integration
|
||||
//!
|
||||
//! This crate provides procedural macros to simplify the integration of Rust structs
|
||||
//! with the Rhai scripting engine. It automatically generates boilerplate code for
|
||||
//! exposing Rust types to Rhai scripts.
|
||||
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
|
||||
mod rhai_api;
|
||||
|
||||
/// Derives the `RhaiApi` for a struct, generating a Rhai DSL module.
|
||||
///
|
||||
/// This macro creates a new module containing a Rhai `export_module` with:
|
||||
/// - A constructor function (`new_<struct_name>()`)
|
||||
/// - Setter functions for each field (chainable API)
|
||||
/// - An `id()` function to retrieve the object's ID
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use derive::RhaiApi;
|
||||
///
|
||||
/// #[derive(RhaiApi)]
|
||||
/// struct MyStruct {
|
||||
/// id: u64,
|
||||
/// name: String,
|
||||
/// value: i32,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This generates a Rhai module that allows scripts like:
|
||||
/// ```rhai
|
||||
/// let obj = new_mystruct().name("test").value(42);
|
||||
/// let obj_id = obj.id();
|
||||
/// ```
|
||||
///
|
||||
/// # Generated Module Structure
|
||||
///
|
||||
/// For a struct `MyStruct`, this creates a module `mystruct_rhai_dsl` containing
|
||||
/// the Rhai-compatible functions. The module can be registered with a Rhai engine
|
||||
/// to expose the functionality to scripts.
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// - Only works with structs that have named fields
|
||||
/// - Fields named `base_data` are ignored during generation
|
||||
/// - The struct must implement an `id()` method returning a numeric type
|
||||
#[proc_macro_derive(RhaiApi)]
|
||||
pub fn rhai_api_derive(input: TokenStream) -> TokenStream {
|
||||
rhai_api::impl_rhai_api(input)
|
||||
}
|
||||
|
||||
/// Derives a `From<T>` implementation for single-element tuple structs.
|
||||
///
|
||||
/// This macro generates a `From` trait implementation that allows converting
|
||||
/// the inner type directly into the tuple struct wrapper.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use derive::FromVec;
|
||||
///
|
||||
/// #[derive(FromVec)]
|
||||
/// struct MyWrapper(Vec<String>);
|
||||
/// ```
|
||||
///
|
||||
/// This generates:
|
||||
/// ```rust
|
||||
/// impl From<Vec<String>> for MyWrapper {
|
||||
/// fn from(vec: Vec<String>) -> Self {
|
||||
/// MyWrapper(vec)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// - Only works with tuple structs containing exactly one field
|
||||
/// - The struct must be a simple wrapper around another type
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This macro will panic at compile time if:
|
||||
/// - Applied to a struct that is not a tuple struct
|
||||
/// - Applied to a tuple struct with more or fewer than one field
|
||||
#[proc_macro_derive(FromVec)]
|
||||
pub fn from_vec_derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = input.ident;
|
||||
|
||||
let inner_type = match input.data {
|
||||
Data::Struct(s) => match s.fields {
|
||||
Fields::Unnamed(mut fields) => {
|
||||
if fields.unnamed.len() != 1 {
|
||||
panic!("FromVec can only be derived for tuple structs with one field.");
|
||||
}
|
||||
let field = fields.unnamed.pop().unwrap().into_value();
|
||||
field.ty
|
||||
}
|
||||
_ => panic!("FromVec can only be derived for tuple structs."),
|
||||
},
|
||||
_ => panic!("FromVec can only be derived for structs."),
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
impl From<#inner_type> for #name {
|
||||
fn from(vec: #inner_type) -> Self {
|
||||
#name(vec)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
116
rhailib/src/derive/src/rhai_api.rs
Normal file
116
rhailib/src/derive/src/rhai_api.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! Implementation of the `RhaiApi` derive macro.
|
||||
//!
|
||||
//! This module contains the core logic for generating Rhai-compatible DSL modules
|
||||
//! from Rust struct definitions.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
|
||||
/// Implements the `RhaiApi` derive macro functionality.
|
||||
///
|
||||
/// This function takes a `TokenStream` representing a struct definition and generates
|
||||
/// a complete Rhai DSL module with constructor, setter functions, and utility methods.
|
||||
///
|
||||
/// # Generated Code Structure
|
||||
///
|
||||
/// For a struct `MyStruct`, this generates:
|
||||
/// - A module named `mystruct_rhai_dsl`
|
||||
/// - A constructor function `new_mystruct()`
|
||||
/// - Setter functions for each field (excluding `base_data`)
|
||||
/// - An `id()` function for object identification
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `input` - A `TokenStream` containing the struct definition to process
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `TokenStream` containing the generated Rhai DSL module code
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if:
|
||||
/// - The input is not a struct
|
||||
/// - The struct does not have named fields
|
||||
pub fn impl_rhai_api(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let struct_name = &input.ident;
|
||||
let struct_name_lowercase_str = struct_name.to_string().to_lowercase();
|
||||
|
||||
let mod_name = format_ident!("{}_rhai_dsl", struct_name_lowercase_str);
|
||||
let id_fn_name = format_ident!("{}_id", struct_name_lowercase_str);
|
||||
|
||||
// --- Generate `new` function ---
|
||||
let new_fn_name_str = format!("new_{}", struct_name_lowercase_str);
|
||||
let new_fn_name_ident = format_ident!("new_{}", struct_name_lowercase_str);
|
||||
let new_fn = quote! {
|
||||
#[rhai_fn(name = #new_fn_name_str, return_raw)]
|
||||
pub fn #new_fn_name_ident() -> Result<RhaiObject, Box<EvalAltResult>> {
|
||||
let object = RhaiObject::new();
|
||||
Ok(object)
|
||||
}
|
||||
};
|
||||
|
||||
// --- Generate setter functions from struct fields ---
|
||||
let fields = if let Data::Struct(s) = &input.data {
|
||||
if let Fields::Named(fields) = &s.fields {
|
||||
&fields.named
|
||||
} else {
|
||||
panic!("RhaiApi can only be derived for structs with named fields.");
|
||||
}
|
||||
} else {
|
||||
panic!("RhaiApi can only be derived for structs.");
|
||||
};
|
||||
|
||||
let setter_fns = fields.iter().map(|f| {
|
||||
let field_name = f.ident.as_ref().unwrap();
|
||||
let field_type = &f.ty;
|
||||
|
||||
if field_name.to_string() == "base_data" {
|
||||
return quote! {};
|
||||
}
|
||||
|
||||
let rhai_fn_name_str = field_name.to_string();
|
||||
let rust_fn_name = format_ident!("{}_{}", struct_name_lowercase_str, field_name);
|
||||
|
||||
quote! {
|
||||
#[rhai_fn(name = #rhai_fn_name_str, return_raw, global, pure)]
|
||||
pub fn #rust_fn_name(
|
||||
object: &mut RhaiObject,
|
||||
value: #field_type,
|
||||
) -> Result<RhaiObject, Box<EvalAltResult>> {
|
||||
let owned_object = std::mem::take(object);
|
||||
*object = owned_object.#field_name(value);
|
||||
Ok(object.clone())
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
pub mod #mod_name {
|
||||
use rhai::plugin::*;
|
||||
use rhai::{EvalAltResult, INT};
|
||||
use super::#struct_name;
|
||||
use std::mem;
|
||||
|
||||
type RhaiObject = #struct_name;
|
||||
|
||||
#[export_module]
|
||||
pub mod generated_rhai_module {
|
||||
use super::*;
|
||||
|
||||
#new_fn
|
||||
|
||||
#[rhai_fn(name = "id", return_raw, global, pure)]
|
||||
pub fn #id_fn_name(object: &mut RhaiObject) -> Result<i64, Box<EvalAltResult>> {
|
||||
Ok(object.id() as i64)
|
||||
}
|
||||
|
||||
#(#setter_fns)*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
Reference in New Issue
Block a user