use std::ffi::{CStr, c_char};
use std::ptr;
use crate::PronunciationDict;
pub type ShabdakoshDict = PronunciationDict;
pub struct ShabdakoshLookupResult {
phoneme_names: Vec<std::ffi::CString>,
count: usize,
}
#[unsafe(no_mangle)]
pub extern "C" fn shabdakosh_dict_english() -> *mut ShabdakoshDict {
Box::into_raw(Box::new(PronunciationDict::english()))
}
#[unsafe(no_mangle)]
pub extern "C" fn shabdakosh_dict_new() -> *mut ShabdakoshDict {
Box::into_raw(Box::new(PronunciationDict::new()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shabdakosh_dict_free(dict: *mut ShabdakoshDict) {
if !dict.is_null() {
drop(unsafe { Box::from_raw(dict) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shabdakosh_dict_len(dict: *const ShabdakoshDict) -> usize {
if dict.is_null() {
return 0;
}
unsafe { &*dict }.len()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shabdakosh_lookup(
dict: *const ShabdakoshDict,
word: *const c_char,
) -> *mut ShabdakoshLookupResult {
if dict.is_null() || word.is_null() {
return ptr::null_mut();
}
let word_str = match unsafe { CStr::from_ptr(word) }.to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
let dict_ref = unsafe { &*dict };
let phonemes = match dict_ref.lookup(word_str) {
Some(p) => p,
None => return ptr::null_mut(),
};
let phoneme_names: Vec<std::ffi::CString> = phonemes
.iter()
.filter_map(|p| {
let ipa = crate::ipa::phoneme_to_ipa(p)?;
std::ffi::CString::new(ipa).ok()
})
.collect();
let count = phoneme_names.len();
Box::into_raw(Box::new(ShabdakoshLookupResult {
phoneme_names,
count,
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shabdakosh_result_phoneme_count(
result: *const ShabdakoshLookupResult,
) -> usize {
if result.is_null() {
return 0;
}
unsafe { &*result }.count
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shabdakosh_result_phoneme_ipa(
result: *const ShabdakoshLookupResult,
index: usize,
) -> *const c_char {
if result.is_null() {
return ptr::null();
}
let result_ref = unsafe { &*result };
match result_ref.phoneme_names.get(index) {
Some(cstr) => cstr.as_ptr(),
None => ptr::null(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn shabdakosh_result_free(result: *mut ShabdakoshLookupResult) {
if !result.is_null() {
drop(unsafe { Box::from_raw(result) });
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ffi_dict_lifecycle() {
let dict = shabdakosh_dict_english();
assert!(!dict.is_null());
let len = unsafe { shabdakosh_dict_len(dict) };
assert!(len >= 10000);
unsafe { shabdakosh_dict_free(dict) };
}
#[test]
fn test_ffi_dict_new() {
let dict = shabdakosh_dict_new();
assert!(!dict.is_null());
let len = unsafe { shabdakosh_dict_len(dict) };
assert_eq!(len, 0);
unsafe { shabdakosh_dict_free(dict) };
}
#[test]
fn test_ffi_lookup_hit() {
let dict = shabdakosh_dict_english();
let word = std::ffi::CString::new("hello").unwrap();
let result = unsafe { shabdakosh_lookup(dict, word.as_ptr()) };
assert!(!result.is_null());
let count = unsafe { shabdakosh_result_phoneme_count(result) };
assert!(count > 0);
let ipa_ptr = unsafe { shabdakosh_result_phoneme_ipa(result, 0) };
assert!(!ipa_ptr.is_null());
let ipa = unsafe { CStr::from_ptr(ipa_ptr) }.to_str().unwrap();
assert!(!ipa.is_empty());
unsafe { shabdakosh_result_free(result) };
unsafe { shabdakosh_dict_free(dict) };
}
#[test]
fn test_ffi_lookup_miss() {
let dict = shabdakosh_dict_english();
let word = std::ffi::CString::new("zxqvbnm").unwrap();
let result = unsafe { shabdakosh_lookup(dict, word.as_ptr()) };
assert!(result.is_null());
unsafe { shabdakosh_dict_free(dict) };
}
#[test]
fn test_ffi_null_safety() {
unsafe {
shabdakosh_dict_free(ptr::null_mut());
assert_eq!(shabdakosh_dict_len(ptr::null()), 0);
assert!(shabdakosh_lookup(ptr::null(), ptr::null()).is_null());
assert_eq!(shabdakosh_result_phoneme_count(ptr::null()), 0);
assert!(shabdakosh_result_phoneme_ipa(ptr::null(), 0).is_null());
shabdakosh_result_free(ptr::null_mut());
}
}
#[test]
fn test_ffi_result_out_of_bounds() {
let dict = shabdakosh_dict_english();
let word = std::ffi::CString::new("hello").unwrap();
let result = unsafe { shabdakosh_lookup(dict, word.as_ptr()) };
assert!(!result.is_null());
let ipa = unsafe { shabdakosh_result_phoneme_ipa(result, 999) };
assert!(ipa.is_null());
unsafe { shabdakosh_result_free(result) };
unsafe { shabdakosh_dict_free(dict) };
}
}