geqslib/
ffi.rs

1use std::ffi::{c_char, c_int, c_void, c_double, c_uint, CStr, CString};
2use std::panic::catch_unwind;
3use std::ptr::{null, copy_nonoverlapping};
4
5use crate::shunting::{ContextHashMap, new_context, ContextLike};
6use crate::solve_equation_with_context;
7use crate::system::{System, SystemBuilder, ConstrainResult};
8
9/// Shorthand for creating an owned string from a C `char *`
10unsafe fn new_owned_string(s: *const c_char) -> String 
11{
12    let c_str = CStr::from_ptr(s);
13    String::from_utf8_lossy(c_str.to_bytes()).to_string()
14} 
15
16/// Converts an owned Rust struct to a pointer that must be manually re-owned or deallocated
17#[inline]
18fn leak_object<T>(obj: T) -> *mut T
19{
20    Box::into_raw(Box::new(obj))
21}
22
23/// Converts a raw pointer to an owned Rust struct before dropping it
24#[inline]
25unsafe fn destroy_object<T>(p_obj: *mut T)
26{
27    let _dropper = Box::from_raw(p_obj);
28}
29
30/// Creates a new empty `ContextHashMap` and returns a C-compatible `void *` to it.
31#[no_mangle]
32pub unsafe extern "C" fn new_context_hash_map() -> *mut c_void
33{
34    leak_object(ContextHashMap::new()) as *mut c_void
35}
36
37/// Creates a new `ContextHashMap` created via `new_context` and returns a C-compatible `void *` to it.
38#[no_mangle]
39pub unsafe extern "C" fn new_default_context_hash_map() -> *mut c_void
40{
41    leak_object(new_context()) as *mut c_void
42}
43
44/// Adds a constant value to the `ContextHashMap` at the given pointer.
45#[no_mangle]
46pub unsafe extern "C" fn add_const_to_ctx(context: *mut c_void, name: *const c_char, val: c_double)
47{
48    let name_str = new_owned_string(name);
49    (*(context as *mut ContextHashMap)).add_const_to_ctx(&name_str, val)
50}
51
52/// Solves a single-unknown equation for a single unknown variable, returning the solution as a
53/// nul-terminated C `char *` on success or `NULL` on failure.
54#[no_mangle]
55pub extern "C" fn solve_equation(equation: *const c_char, context: *const c_void, guess: c_double, min: c_double, max: c_double, margin: c_double, limit: c_uint) -> *const c_char
56{
57    let res = catch_unwind(|| {
58        let equation_str = unsafe { new_owned_string(equation) };
59
60        let mut ctx = ContextHashMap::new();
61        unsafe { copy_nonoverlapping(context as *const ContextHashMap, &mut ctx, 1) };
62
63        let (var, val) = match solve_equation_with_context(&equation_str, &mut ctx, guess, min, max, margin, limit as usize)
64        {
65            Ok(s) => s,
66            Err(_) => return null() as *const c_char,
67        };
68
69        // Create a nul-terminated string with the solution data
70        let soln_str: CString = CString::new(format!("{}={}", var, val))
71            .expect("failed to create C-compatible solution string!");
72
73        soln_str.into_raw()
74    });
75
76    match res
77    {
78        Ok(s) => s,
79        Err(_) => null() as *const c_char,
80    }
81}
82
83/// Allocates a new `SystemBuilder` object on the Rust side of the FFI and returns a raw pointer to it.
84#[no_mangle]
85pub extern "C" fn new_system_builder(equation: *const c_char, context: *const c_void) -> *const c_void
86{
87    let res = catch_unwind(|| {
88        let equation_str = unsafe { new_owned_string(equation) };
89        
90        let ctx = unsafe { (*(context as *const ContextHashMap)).clone() };
91
92        let builder = match SystemBuilder::new(&equation_str, ctx)
93        {
94            Ok(x) => x,
95            Err(_) => return null(),
96        };
97
98        leak_object(builder)
99    });
100
101    match res
102    {
103        Ok(p) => p as *const c_void,
104        Err(_) => null(),
105    }
106}
107
108/// Tries to constrain the system with an equation given as a nul-terminated C `char *`.
109/// The returned C `int` value indicates the following:
110/// 
111/// - `0`: The equation did not further constrain the system and was not added
112/// - `1`: The equation further constrained the system and was added successfully
113/// - `2`: The equation will over-constrain the system and was not added
114/// - `-1`: An error occurred while trying to constrain the system
115#[no_mangle]
116pub extern "C" fn try_constrain_with(p_builder: *mut c_void, equation: *const c_char) -> c_int
117{
118    let res = catch_unwind(|| {
119        let builder = p_builder as *mut SystemBuilder;
120        let equation_str = unsafe { new_owned_string(equation) };
121        let constrain_res = unsafe { (*builder).try_constrain_with(&equation_str) };
122
123        match constrain_res
124        {
125            Ok(ConstrainResult::WillConstrain) => 1,
126            Ok(ConstrainResult::WillNotConstrain) => 0,
127            Ok(ConstrainResult::WillOverConstrain) => 2,
128            Err(_) => -1
129        }
130    });
131    
132    res.unwrap_or(-1)
133}
134
135/// Tries to check whether the system is constrained or not. The returned C `int` value 
136/// indicates the following:
137/// - `0`: The system is not fully constrained 
138/// - `1`: The system is fully constrained
139/// - `-1`: An error occurred while checking the system
140#[no_mangle]
141pub extern "C" fn is_fully_constrained(p_builder: *mut c_void) -> c_int
142{
143    let res = catch_unwind(|| {
144        unsafe{ (*(p_builder as *mut SystemBuilder)).is_fully_constrained() }
145    });
146
147    match res
148    {
149        Ok(x) => if x { 1 } else { 0 },
150        Err(_) => -1,
151    }
152}
153
154/// Tries to create a system from a `SystemBuilder` located at the given pointer,
155/// returning a pointer to the created `System` if successful or `NULL` if not.
156#[no_mangle]
157pub extern "C" fn build_system(p_builder: *mut c_void) -> *const c_void
158{
159    let res = catch_unwind(|| {
160        let builder = unsafe { Box::from_raw(p_builder as *mut SystemBuilder) };
161        let system = match builder.build_system()
162        {
163            Some(s) => s,
164            None => return null(),
165        };
166
167        leak_object(system)
168    });
169
170    match res 
171    {
172        Ok(p) => p as *const c_void,
173        Err(_) => null(),
174    }
175}
176
177/// Prints information about a `SystemBuilder` for debugging purposes.
178#[no_mangle]
179pub unsafe extern "C" fn debug_system_builder(p_builder: *const c_void)
180{
181    println!("{:#?}", *(p_builder as *const SystemBuilder));
182}
183
184/// Specifies a guess and domain for a given variable in the `System` at the given pointer.
185/// 
186/// The returned C `int` value indicates the following:
187/// - `1`: The values were specified successfully
188/// - `-1`: An error occurred while specifying the domain or guess value 
189#[no_mangle]
190pub extern "C" fn specify_variable(p_system: *mut c_void, var: *const c_char, guess: c_double, min: c_double, max: c_double) -> c_int
191{
192    let res = catch_unwind(|| {
193        unsafe
194        {    
195            let var_str = new_owned_string(var);
196            (*(p_system as *mut System)).specify_variable(&var_str, guess, min, max);
197        }
198    });
199
200    match res
201    {
202        Ok(_) => 1,
203        Err(_) => -1,
204    }
205}
206
207/// Tries to solve the system of equations to within the radius `margin` 
208/// of the actual solution in `limit` iterations, returning a C `char *` containing the 
209/// solution to the system or `NULL` if the solution failed.
210#[no_mangle]
211pub extern "C" fn solve_system(p_system: *mut c_void, margin: c_double, limit: c_uint) -> *const c_char
212{
213    let res = catch_unwind(|| {
214        let system = unsafe { Box::from_raw(p_system as *mut System) };
215
216        let soln = match system.solve(margin, limit as usize)
217        {
218            Ok(s) => s,
219            Err(_) => return null() as *const c_char,
220        };
221
222        // Create a nul-terminated string with the solution data
223        let soln_str: CString = CString::new(
224            soln.iter()
225                .map(|(var, val)| format!("{}={}", var, val))
226                .collect::<Vec<String>>()
227                .join("\n")
228        ).expect("failed to create C-compatible solution string!");
229
230        soln_str.into_raw()
231    });
232
233    match res 
234    {
235        Ok(s) => s,
236        Err(_) => null() as *const c_char,
237    }
238}
239
240/// Frees a `ContextHashMap` object at the given pointer
241#[no_mangle]
242pub unsafe extern "C" fn free_context_hash_map(p_context: *mut c_void)
243{
244    destroy_object(p_context as *mut ContextHashMap);
245}
246
247/// Frees a `SystemBuilder` object at the given pointer
248#[no_mangle]
249pub unsafe extern "C" fn free_system_builder(p_builder: *mut c_void)
250{
251    destroy_object(p_builder as *mut SystemBuilder);
252}
253
254/// Frees a `System` object at the given pointer
255#[no_mangle]
256pub unsafe extern "C" fn free_system(p_system: *mut c_void)
257{
258    destroy_object(p_system as *mut System);
259}
260
261/// Frees the nul-terminated `char *` given
262#[no_mangle]
263pub unsafe extern "C" fn free_solution_string(soln_str: *mut c_char)
264{
265    let _owned = CString::from_raw(soln_str);
266}