1use std::borrow::Cow;
4use std::cell::RefCell;
5use std::ffi::{c_char, CString};
6use std::mem::{size_of, MaybeUninit};
7
8use msime_engine::EngineError;
9
10use crate::{
11 MSIME_ENGINE_INVALID_ARGUMENT, MSIME_ENGINE_NULL, MSIME_ENGINE_OK, MSIME_ENGINE_PANIC,
12};
13
14#[repr(C)]
16#[derive(Debug, Clone, Copy)]
17pub struct msime_engine_str {
18 pub ptr: *const c_char,
19 pub len: usize,
20}
21
22impl msime_engine_str {
23 pub(crate) const EMPTY: Self = Self {
24 ptr: c"".as_ptr(),
25 len: 0,
26 };
27
28 pub(crate) fn borrow(text: &str) -> Self {
29 if text.is_empty() {
30 return Self::EMPTY;
31 }
32 Self {
33 ptr: text.as_ptr().cast(),
34 len: text.len(),
35 }
36 }
37}
38
39#[repr(C)]
41#[derive(Debug, Clone, Copy)]
42pub struct msime_engine_result {
43 pub struct_size: u32,
44 pub handled: u32,
46 pub has_commit: u32,
48 pub diagnostic_code: u32,
50 pub revision: u64,
52 pub commit: msime_engine_str,
53 pub diagnostic: msime_engine_str,
55}
56
57#[repr(C)]
59#[derive(Debug, Clone, Copy)]
60pub struct msime_engine_state {
61 pub struct_size: u32,
62 pub scheme: u32,
64 pub revision: u64,
66 pub candidate_count: u32,
67 pub flags: u32,
69 pub local_mode: u32,
71 pub caret_byte: u32,
73 pub conversion_focus_start_scalar: u32,
75 pub conversion_focus_end_scalar: u32,
76 pub preedit: msime_engine_str,
78 pub editing_text: msime_engine_str,
80 pub conversion: msime_engine_str,
82 pub spelling_symbols: msime_engine_str,
84}
85
86#[repr(C)]
88#[derive(Debug, Clone, Copy)]
89pub struct msime_engine_candidate {
90 pub struct_size: u32,
91 pub index: u32,
93 pub source: u32,
95 pub scheme: u32,
97 pub flags: u32,
99 pub fixed_position: i32,
101 pub word: msime_engine_str,
102 pub code: msime_engine_str,
104 pub annotation: msime_engine_str,
106 pub corrected_from: msime_engine_str,
108}
109
110#[repr(C)]
112#[derive(Debug, Clone, Copy)]
113pub struct msime_engine_paths {
114 pub struct_size: u32,
115 pub resources: msime_engine_str,
117 pub user_data: msime_engine_str,
119 pub cache: msime_engine_str,
121 pub dictionaries: msime_engine_str,
123}
124
125pub(crate) trait Sized1: Copy {
127 const V1_SIZE: usize;
129}
130
131macro_rules! sized1 {
132 ($($name:ident),*) => {$(
133 impl Sized1 for $name {
134 const V1_SIZE: usize = size_of::<$name>();
135 }
136 )*};
137}
138sized1!(
139 msime_engine_result,
140 msime_engine_state,
141 msime_engine_candidate,
142 msime_engine_paths
143);
144
145const _: () = {
147 let word = size_of::<msime_engine_str>();
148 assert!(size_of::<msime_engine_result>() == 4 * 4 + 8 + 2 * word);
149 assert!(size_of::<msime_engine_state>() == 4 * 2 + 8 + 4 * 6 + 4 * word);
150 assert!(size_of::<msime_engine_candidate>() == 4 * 6 + 4 * word);
151};
152
153#[derive(Debug)]
155pub(crate) struct Failure {
156 pub code: i32,
157 pub message: Cow<'static, str>,
158}
159
160impl Failure {
161 pub(crate) fn new(code: i32, message: impl Into<Cow<'static, str>>) -> Self {
162 Self {
163 code,
164 message: message.into(),
165 }
166 }
167
168 pub(crate) fn invalid(message: impl Into<Cow<'static, str>>) -> Self {
169 Self::new(MSIME_ENGINE_INVALID_ARGUMENT, message)
170 }
171
172 pub(crate) fn null(what: &'static str) -> Self {
173 Self::new(MSIME_ENGINE_NULL, what)
174 }
175}
176
177impl From<EngineError> for Failure {
178 fn from(error: EngineError) -> Self {
179 Self::new(error.code().value() as i32, error.to_string())
180 }
181}
182
183pub(crate) type Outcome<T = ()> = Result<T, Failure>;
184
185thread_local! {
186 static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
188}
189
190pub(crate) fn last_error() -> *const c_char {
192 LAST_ERROR.with(|slot| {
193 slot.borrow()
194 .as_ref()
195 .map_or(c"".as_ptr(), |message| message.as_ptr())
196 })
197}
198
199fn set_last_error(message: Option<&str>) {
200 let message = message.map(|text| {
201 CString::new(text.replace('\0', "\u{fffd}")).unwrap_or_default()
203 });
204 LAST_ERROR.with(|slot| *slot.borrow_mut() = message);
205}
206
207pub(crate) fn guard(body: impl FnOnce() -> Outcome) -> i32 {
209 set_last_error(None);
210 let outcome = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)) {
211 Ok(outcome) => outcome,
212 Err(payload) => Err(Failure::new(
213 MSIME_ENGINE_PANIC,
214 panic_message(payload.as_ref()),
215 )),
216 };
217 match outcome {
218 Ok(()) => MSIME_ENGINE_OK,
219 Err(failure) => {
220 set_last_error(Some(&failure.message));
221 failure.code
222 }
223 }
224}
225
226pub(crate) fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
227 let detail = payload
228 .downcast_ref::<&str>()
229 .copied()
230 .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
231 .unwrap_or("non-string payload");
232 format!("internal panic: {detail}")
233}
234
235pub(crate) unsafe fn input_str<'a>(
240 ptr: *const c_char,
241 len: usize,
242 what: &'static str,
243) -> Outcome<&'a str> {
244 if ptr.is_null() {
245 return if len == 0 {
246 Ok("")
247 } else {
248 Err(Failure::null(what))
249 };
250 }
251 let bytes = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len) };
253 std::str::from_utf8(bytes).map_err(|_| Failure::invalid(format!("{what} is not UTF-8")))
254}
255
256pub(crate) unsafe fn field_str<'a>(
261 value: msime_engine_str,
262 what: &'static str,
263) -> Outcome<&'a str> {
264 unsafe { input_str(value.ptr, value.len, what) }
266}
267
268pub(crate) unsafe fn read_sized<T: Sized1>(input: *const T, what: &'static str) -> Outcome<T> {
273 if input.is_null() {
274 return Err(Failure::null(what));
275 }
276 let caller = unsafe { input.cast::<u32>().read() } as usize;
278 if caller < T::V1_SIZE {
279 return Err(Failure::invalid(format!(
280 "{what}: struct_size is smaller than the ABI 1.0 layout"
281 )));
282 }
283 let mut value = MaybeUninit::<T>::zeroed();
284 unsafe {
286 std::ptr::copy_nonoverlapping(
287 input.cast::<u8>(),
288 value.as_mut_ptr().cast::<u8>(),
289 caller.min(size_of::<T>()),
290 );
291 Ok(value.assume_init())
292 }
293}
294
295unsafe fn output_size<T: Sized1>(output: *const T, what: &'static str) -> Outcome<usize> {
300 let caller = unsafe { output.cast::<u32>().read() } as usize;
302 if caller < T::V1_SIZE {
303 return Err(Failure::invalid(format!(
304 "{what}: struct_size is smaller than the ABI 1.0 layout"
305 )));
306 }
307 Ok(caller)
308}
309
310pub(crate) unsafe fn check_output<T: Sized1>(output: *const T, what: &'static str) -> Outcome {
315 if output.is_null() {
316 return Ok(());
317 }
318 unsafe { output_size(output, what) }.map(|_| ())
320}
321
322pub(crate) unsafe fn write_sized<T: Sized1>(
327 output: *mut T,
328 mut value: T,
329 what: &'static str,
330) -> Outcome {
331 if output.is_null() {
332 return Err(Failure::null(what));
333 }
334 let caller = unsafe { output_size(output, what) }?;
336 let written = caller.min(size_of::<T>());
337 unsafe {
339 std::ptr::addr_of_mut!(value)
340 .cast::<u32>()
341 .write(written as u32)
342 };
343 unsafe {
345 std::ptr::copy_nonoverlapping(
346 std::ptr::addr_of!(value).cast::<u8>(),
347 output.cast::<u8>(),
348 written,
349 );
350 if caller > written {
351 std::ptr::write_bytes(output.cast::<u8>().add(written), 0, caller - written);
352 }
353 }
354 Ok(())
355}
356
357pub(crate) fn owned_string(text: String) -> *mut c_char {
359 CString::new(text.replace('\0', "\u{fffd}"))
360 .unwrap_or_default()
361 .into_raw()
362}
363
364pub(crate) unsafe fn write_owned(output: *mut *mut c_char, text: String) {
369 if !output.is_null() {
370 unsafe { output.write(owned_string(text)) };
372 }
373}