1use std::ffi::c_char;
26use std::path::Path;
27
28use anyhow::Result;
29
30use crate::DynLib;
31
32pub struct VTablePlugin<T: Copy> {
38 _lib: DynLib,
39 vtable: T,
40}
41
42impl<T: Copy> VTablePlugin<T> {
43 pub unsafe fn load(path: &Path, symbol: &[u8]) -> Result<Self> {
50 let lib = unsafe { DynLib::load(path) }?;
51 unsafe { Self::from_lib(lib, symbol) }
52 }
53
54 pub unsafe fn from_lib(lib: DynLib, symbol: &[u8]) -> Result<Self> {
58 let getter: unsafe extern "C" fn() -> *const T = unsafe { lib.symbol(symbol) }?;
59 let vtable = unsafe { getter() };
60 let vtable = unsafe { vtable.as_ref() }
61 .copied()
62 .ok_or_else(|| anyhow::anyhow!("vtable getter returned null"))?;
63 Ok(Self { _lib: lib, vtable })
64 }
65
66 pub fn vtable(&self) -> &T {
67 &self.vtable
68 }
69}
70
71pub type MathSession = *mut std::ffi::c_void;
77
78#[repr(C)]
80#[derive(Debug, Clone, Copy)]
81pub struct GeneratedFunction {
82 pub name: *const c_char,
83 pub signature: *const c_char,
84 pub arg_count: u32,
85 pub id: u32,
86}
87
88#[repr(C)]
90#[derive(Debug, Clone, Copy)]
91pub struct MathModuleVtable {
92 pub create_session: unsafe extern "C" fn() -> MathSession,
94 pub destroy_session: unsafe extern "C" fn(MathSession),
95
96 pub add_i32: unsafe extern "C" fn(MathSession, i32, i32) -> i32,
98 pub add_i64: unsafe extern "C" fn(MathSession, i64, i64) -> i64,
99 pub add_f64: unsafe extern "C" fn(MathSession, f64, f64) -> f64,
100 pub mul_i32: unsafe extern "C" fn(MathSession, i32, i32) -> i32,
101 pub mul_f64: unsafe extern "C" fn(MathSession, f64, f64) -> f64,
102 pub pi: unsafe extern "C" fn(MathSession) -> f64,
103 pub tau: unsafe extern "C" fn(MathSession) -> f64,
104
105 pub generated_count: unsafe extern "C" fn(MathSession) -> u32,
107 pub generated_at: unsafe extern "C" fn(MathSession, u32) -> GeneratedFunction,
108
109 pub module_name: unsafe extern "C" fn() -> *const c_char,
111 pub module_version: unsafe extern "C" fn() -> u32,
112}
113
114unsafe impl Send for MathModuleVtable {}
116unsafe impl Sync for MathModuleVtable {}
117
118#[cfg(test)]
119mod cpp_math_tests {
120 use super::*;
121 use std::ffi::CStr;
122 use std::path::PathBuf;
123
124 fn math_module_path() -> PathBuf {
125 let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
127 p.push("../../cpp/build/math_module");
128 p.push("libmath_module.so");
129 p
130 }
131
132 #[test]
133 fn load_cpp_math_module_via_vtable() {
134 let path = math_module_path();
135 if !path.exists() {
136 eprintln!("SKIP: {} not found — build cpp/ first", path.display());
137 return;
138 }
139
140 let plugin =
141 unsafe { VTablePlugin::<MathModuleVtable>::load(&path, b"math_module_get_vtable\0") }
142 .expect("failed to load math_module");
143 let vt = plugin.vtable();
144
145 unsafe {
147 let name = CStr::from_ptr((vt.module_name)());
148 assert_eq!(name.to_str().unwrap(), "core-ast-math");
149 assert_eq!((vt.module_version)(), 1);
150 }
151
152 let session = unsafe { (vt.create_session)() };
154 assert!(!session.is_null());
155
156 assert_eq!(unsafe { (vt.generated_count)(session) }, 0);
158
159 let result = unsafe { (vt.add_i32)(session, 10, 20) };
161 assert_eq!(result, 30);
162 assert_eq!(unsafe { (vt.generated_count)(session) }, 1);
163
164 let result = unsafe { (vt.mul_f64)(session, 3.0, 7.0) };
166 assert!((result - 21.0).abs() < 1e-10);
167 assert_eq!(unsafe { (vt.generated_count)(session) }, 2);
168
169 let pi_val = unsafe { (vt.pi)(session) };
171 assert!((pi_val - std::f64::consts::PI).abs() < 1e-10);
172 assert_eq!(unsafe { (vt.generated_count)(session) }, 3);
173
174 let func0 = unsafe { (vt.generated_at)(session, 0) };
176 let func0_name = unsafe { CStr::from_ptr(func0.name) }.to_str().unwrap();
177 assert_eq!(func0_name, "add_i32");
178
179 let func1 = unsafe { (vt.generated_at)(session, 1) };
180 let func1_name = unsafe { CStr::from_ptr(func1.name) }.to_str().unwrap();
181 assert_eq!(func1_name, "mul_f64");
182
183 let func2 = unsafe { (vt.generated_at)(session, 2) };
184 let func2_name = unsafe { CStr::from_ptr(func2.name) }.to_str().unwrap();
185 assert_eq!(func2_name, "pi");
186
187 let _ = unsafe { (vt.add_i32)(session, 1, 2) };
189 assert_eq!(unsafe { (vt.generated_count)(session) }, 3);
190
191 unsafe { (vt.destroy_session)(session) };
193 }
194}