use std::borrow::Cow;
use std::cell::RefCell;
use std::ffi::{c_char, CString};
use std::mem::{size_of, MaybeUninit};
use msime_engine::EngineError;
use crate::{
MSIME_ENGINE_INVALID_ARGUMENT, MSIME_ENGINE_NULL, MSIME_ENGINE_OK, MSIME_ENGINE_PANIC,
};
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct msime_engine_str {
pub ptr: *const c_char,
pub len: usize,
}
impl msime_engine_str {
pub(crate) const EMPTY: Self = Self {
ptr: c"".as_ptr(),
len: 0,
};
pub(crate) fn borrow(text: &str) -> Self {
if text.is_empty() {
return Self::EMPTY;
}
Self {
ptr: text.as_ptr().cast(),
len: text.len(),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct msime_engine_result {
pub struct_size: u32,
pub handled: u32,
pub has_commit: u32,
pub diagnostic_code: u32,
pub revision: u64,
pub commit: msime_engine_str,
pub diagnostic: msime_engine_str,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct msime_engine_state {
pub struct_size: u32,
pub scheme: u32,
pub revision: u64,
pub candidate_count: u32,
pub flags: u32,
pub local_mode: u32,
pub caret_byte: u32,
pub conversion_focus_start_scalar: u32,
pub conversion_focus_end_scalar: u32,
pub preedit: msime_engine_str,
pub editing_text: msime_engine_str,
pub conversion: msime_engine_str,
pub spelling_symbols: msime_engine_str,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct msime_engine_candidate {
pub struct_size: u32,
pub index: u32,
pub source: u32,
pub scheme: u32,
pub flags: u32,
pub fixed_position: i32,
pub word: msime_engine_str,
pub code: msime_engine_str,
pub annotation: msime_engine_str,
pub corrected_from: msime_engine_str,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct msime_engine_paths {
pub struct_size: u32,
pub resources: msime_engine_str,
pub user_data: msime_engine_str,
pub cache: msime_engine_str,
pub dictionaries: msime_engine_str,
}
pub(crate) trait Sized1: Copy {
const V1_SIZE: usize;
}
macro_rules! sized1 {
($($name:ident),*) => {$(
impl Sized1 for $name {
const V1_SIZE: usize = size_of::<$name>();
}
)*};
}
sized1!(
msime_engine_result,
msime_engine_state,
msime_engine_candidate,
msime_engine_paths
);
const _: () = {
let word = size_of::<msime_engine_str>();
assert!(size_of::<msime_engine_result>() == 4 * 4 + 8 + 2 * word);
assert!(size_of::<msime_engine_state>() == 4 * 2 + 8 + 4 * 6 + 4 * word);
assert!(size_of::<msime_engine_candidate>() == 4 * 6 + 4 * word);
};
#[derive(Debug)]
pub(crate) struct Failure {
pub code: i32,
pub message: Cow<'static, str>,
}
impl Failure {
pub(crate) fn new(code: i32, message: impl Into<Cow<'static, str>>) -> Self {
Self {
code,
message: message.into(),
}
}
pub(crate) fn invalid(message: impl Into<Cow<'static, str>>) -> Self {
Self::new(MSIME_ENGINE_INVALID_ARGUMENT, message)
}
pub(crate) fn null(what: &'static str) -> Self {
Self::new(MSIME_ENGINE_NULL, what)
}
}
impl From<EngineError> for Failure {
fn from(error: EngineError) -> Self {
Self::new(error.code().value() as i32, error.to_string())
}
}
pub(crate) type Outcome<T = ()> = Result<T, Failure>;
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
}
pub(crate) fn last_error() -> *const c_char {
LAST_ERROR.with(|slot| {
slot.borrow()
.as_ref()
.map_or(c"".as_ptr(), |message| message.as_ptr())
})
}
fn set_last_error(message: Option<&str>) {
let message = message.map(|text| {
CString::new(text.replace('\0', "\u{fffd}")).unwrap_or_default()
});
LAST_ERROR.with(|slot| *slot.borrow_mut() = message);
}
pub(crate) fn guard(body: impl FnOnce() -> Outcome) -> i32 {
set_last_error(None);
let outcome = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)) {
Ok(outcome) => outcome,
Err(payload) => Err(Failure::new(
MSIME_ENGINE_PANIC,
panic_message(payload.as_ref()),
)),
};
match outcome {
Ok(()) => MSIME_ENGINE_OK,
Err(failure) => {
set_last_error(Some(&failure.message));
failure.code
}
}
}
pub(crate) fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
let detail = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("non-string payload");
format!("internal panic: {detail}")
}
pub(crate) unsafe fn input_str<'a>(
ptr: *const c_char,
len: usize,
what: &'static str,
) -> Outcome<&'a str> {
if ptr.is_null() {
return if len == 0 {
Ok("")
} else {
Err(Failure::null(what))
};
}
let bytes = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len) };
std::str::from_utf8(bytes).map_err(|_| Failure::invalid(format!("{what} is not UTF-8")))
}
pub(crate) unsafe fn field_str<'a>(
value: msime_engine_str,
what: &'static str,
) -> Outcome<&'a str> {
unsafe { input_str(value.ptr, value.len, what) }
}
pub(crate) unsafe fn read_sized<T: Sized1>(input: *const T, what: &'static str) -> Outcome<T> {
if input.is_null() {
return Err(Failure::null(what));
}
let caller = unsafe { input.cast::<u32>().read() } as usize;
if caller < T::V1_SIZE {
return Err(Failure::invalid(format!(
"{what}: struct_size is smaller than the ABI 1.0 layout"
)));
}
let mut value = MaybeUninit::<T>::zeroed();
unsafe {
std::ptr::copy_nonoverlapping(
input.cast::<u8>(),
value.as_mut_ptr().cast::<u8>(),
caller.min(size_of::<T>()),
);
Ok(value.assume_init())
}
}
unsafe fn output_size<T: Sized1>(output: *const T, what: &'static str) -> Outcome<usize> {
let caller = unsafe { output.cast::<u32>().read() } as usize;
if caller < T::V1_SIZE {
return Err(Failure::invalid(format!(
"{what}: struct_size is smaller than the ABI 1.0 layout"
)));
}
Ok(caller)
}
pub(crate) unsafe fn check_output<T: Sized1>(output: *const T, what: &'static str) -> Outcome {
if output.is_null() {
return Ok(());
}
unsafe { output_size(output, what) }.map(|_| ())
}
pub(crate) unsafe fn write_sized<T: Sized1>(
output: *mut T,
mut value: T,
what: &'static str,
) -> Outcome {
if output.is_null() {
return Err(Failure::null(what));
}
let caller = unsafe { output_size(output, what) }?;
let written = caller.min(size_of::<T>());
unsafe {
std::ptr::addr_of_mut!(value)
.cast::<u32>()
.write(written as u32)
};
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::addr_of!(value).cast::<u8>(),
output.cast::<u8>(),
written,
);
if caller > written {
std::ptr::write_bytes(output.cast::<u8>().add(written), 0, caller - written);
}
}
Ok(())
}
pub(crate) fn owned_string(text: String) -> *mut c_char {
CString::new(text.replace('\0', "\u{fffd}"))
.unwrap_or_default()
.into_raw()
}
pub(crate) unsafe fn write_owned(output: *mut *mut c_char, text: String) {
if !output.is_null() {
unsafe { output.write(owned_string(text)) };
}
}