automerge/automerge-c/src/result_stack.rs
2022-08-23 06:04:22 -07:00

156 lines
5.4 KiB
Rust

use crate::result::{AMfree, AMresult, AMresultStatus, AMresultValue, AMstatus, AMvalue};
/// \struct AMresultStack
/// \installed_headerfile
/// \brief A node in a singly-linked list of result pointers.
///
/// \note Using this data structure is purely optional because its only purpose
/// is to make memory management tolerable for direct usage of this API
/// in C, C++ and Objective-C.
#[repr(C)]
pub struct AMresultStack {
/// A result to be deallocated.
pub result: *mut AMresult,
/// The next node in the singly-linked list or `NULL`.
pub next: *mut AMresultStack,
}
impl AMresultStack {
pub fn new(result: *mut AMresult, next: *mut AMresultStack) -> Self {
Self { result, next }
}
}
/// \memberof AMresultStack
/// \brief Deallocates the storage for a stack of results.
///
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
/// \return The number of `AMresult` structs freed.
/// \pre \p stack `!= NULL`.
/// \post `*stack == NULL`.
/// \note Calling this function is purely optional because its only purpose is
/// to make memory management tolerable for direct usage of this API in
/// C, C++ and Objective-C.
/// \internal
///
/// # Safety
/// stack must be a valid AMresultStack pointer pointer
#[no_mangle]
pub unsafe extern "C" fn AMfreeStack(stack: *mut *mut AMresultStack) -> usize {
if stack.is_null() {
return 0;
}
let mut count: usize = 0;
while !(*stack).is_null() {
AMfree(AMpop(stack));
count += 1;
}
count
}
/// \memberof AMresultStack
/// \brief Gets the topmost result from the stack after removing it.
///
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
/// \return A pointer to an `AMresult` struct or `NULL`.
/// \pre \p stack `!= NULL`.
/// \post `*stack == NULL`.
/// \note Calling this function is purely optional because its only purpose is
/// to make memory management tolerable for direct usage of this API in
/// C, C++ and Objective-C.
/// \internal
///
/// # Safety
/// stack must be a valid AMresultStack pointer pointer
#[no_mangle]
pub unsafe extern "C" fn AMpop(stack: *mut *mut AMresultStack) -> *mut AMresult {
if stack.is_null() || (*stack).is_null() {
return std::ptr::null_mut();
}
let top = Box::from_raw(*stack);
*stack = top.next;
let result = top.result;
drop(top);
result
}
/// \memberof AMresultStack
/// \brief The prototype of a function to be called when a value matching the
/// given discriminant cannot be extracted from the result at the top of
/// the given stack.
///
/// \note Implementing this function is purely optional because its only purpose
/// is to make memory management tolerable for direct usage of this API
/// in C, C++ and Objective-C.
pub type AMpushCallback =
Option<extern "C" fn(stack: *mut *mut AMresultStack, discriminant: u8) -> ()>;
/// \memberof AMresultStack
/// \brief Pushes the given result onto the given stack and then either extracts
/// a value matching the given discriminant from that result or,
/// failing that, calls the given function and gets a void value instead.
///
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
/// \param[in] result A pointer to an `AMresult` struct.
/// \param[in] discriminant An `AMvalue` variant's corresponding enum tag.
/// \param[in] callback A pointer to a function with the same signature as
/// `AMpushCallback()` or `NULL`.
/// \return An `AMvalue` struct.
/// \pre \p stack `!= NULL`.
/// \pre \p result `!= NULL`.
/// \warning If \p stack `== NULL` then \p result is deallocated in order to
/// prevent a memory leak.
/// \note Calling this function is purely optional because its only purpose is
/// to make memory management tolerable for direct usage of this API in
/// C, C++ and Objective-C.
/// \internal
///
/// # Safety
/// stack must be a valid AMresultStack pointer pointer
/// result must be a valid AMresult pointer
#[no_mangle]
pub unsafe extern "C" fn AMpush<'a>(
stack: *mut *mut AMresultStack,
result: *mut AMresult,
discriminant: u8,
callback: AMpushCallback,
) -> AMvalue<'a> {
if stack.is_null() {
// There's no stack to push the result onto so it has to be freed in
// order to prevent a memory leak.
AMfree(result);
if let Some(callback) = callback {
callback(stack, discriminant);
}
return AMvalue::Void;
} else if result.is_null() {
if let Some(callback) = callback {
callback(stack, discriminant);
}
return AMvalue::Void;
}
// Always push the result onto the stack, even if it's wrong, so that the
// given callback can retrieve it.
let node = Box::new(AMresultStack::new(result, *stack));
let top = Box::into_raw(node);
*stack = top;
// Test that the result contains a value.
match AMresultStatus(result) {
AMstatus::Ok => {}
_ => {
if let Some(callback) = callback {
callback(stack, discriminant);
}
return AMvalue::Void;
}
}
// Test that the result's value matches the given discriminant.
let value = AMresultValue(result);
if discriminant != u8::from(&value) {
if let Some(callback) = callback {
callback(stack, discriminant);
}
return AMvalue::Void;
}
value
}