Skip to main content

speechmarkdown_rust/
ffi.rs

1use std::cell::RefCell;
2use std::ffi::{CStr, CString};
3use std::os::raw::c_char;
4
5use crate::formatters::base::Platform;
6use crate::parser::SpeechMarkdownParser;
7
8thread_local! {
9    static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
10}
11
12fn set_last_error(msg: &str) {
13    LAST_ERROR.with(|e| {
14        *e.borrow_mut() = Some(
15            CString::new(msg)
16                .unwrap_or_else(|_| CString::new("error message contained null byte").unwrap()),
17        );
18    });
19}
20
21fn parse_platform(platform: *const c_char) -> Option<Platform> {
22    if platform.is_null() {
23        set_last_error("platform argument is null");
24        return None;
25    }
26    let platform_str = unsafe { CStr::from_ptr(platform) }.to_str().unwrap_or("");
27    match Platform::from_platform_str(platform_str) {
28        Some(p) => Some(p),
29        None => {
30            set_last_error(&format!("unsupported platform: '{}'", platform_str));
31            None
32        }
33    }
34}
35
36fn to_c_string(s: String) -> *mut c_char {
37    match CString::new(s) {
38        Ok(cs) => cs.into_raw(),
39        Err(_) => {
40            set_last_error("output contained null byte");
41            std::ptr::null_mut()
42        }
43    }
44}
45
46/// # Safety
47/// `input` and `platform` must be valid pointers to null-terminated C strings.
48#[no_mangle]
49pub unsafe extern "C" fn speechmarkdown_to_ssml(
50    input: *const c_char,
51    platform: *const c_char,
52) -> *mut c_char {
53    if input.is_null() {
54        set_last_error("input argument is null");
55        return std::ptr::null_mut();
56    }
57
58    let input_str = match CStr::from_ptr(input).to_str() {
59        Ok(s) => s,
60        Err(_) => {
61            set_last_error("input is not valid UTF-8");
62            return std::ptr::null_mut();
63        }
64    };
65
66    let platform_val = match parse_platform(platform) {
67        Some(p) => p,
68        None => return std::ptr::null_mut(),
69    };
70
71    match SpeechMarkdownParser::to_ssml(input_str, platform_val) {
72        Ok(ssml) => to_c_string(ssml),
73        Err(e) => {
74            set_last_error(&e.to_string());
75            std::ptr::null_mut()
76        }
77    }
78}
79
80/// # Safety
81/// `input` must be a valid pointer to a null-terminated C string.
82#[no_mangle]
83pub unsafe extern "C" fn speechmarkdown_to_text(input: *const c_char) -> *mut c_char {
84    if input.is_null() {
85        set_last_error("input argument is null");
86        return std::ptr::null_mut();
87    }
88
89    let input_str = match CStr::from_ptr(input).to_str() {
90        Ok(s) => s,
91        Err(_) => {
92            set_last_error("input is not valid UTF-8");
93            return std::ptr::null_mut();
94        }
95    };
96
97    match SpeechMarkdownParser::to_text(input_str) {
98        Ok(text) => to_c_string(text),
99        Err(e) => {
100            set_last_error(&e.to_string());
101            std::ptr::null_mut()
102        }
103    }
104}
105
106/// # Safety
107/// `s` must be a valid pointer to a `CString` previously returned by this library, or null.
108#[no_mangle]
109pub unsafe extern "C" fn speechmarkdown_free(s: *mut c_char) {
110    if !s.is_null() {
111        drop(CString::from_raw(s));
112    }
113}
114
115#[no_mangle]
116pub extern "C" fn speechmarkdown_get_error() -> *mut c_char {
117    LAST_ERROR.with(|e| match e.borrow().as_ref() {
118        Some(cs) => {
119            let copy = cs.clone();
120            copy.into_raw()
121        }
122        None => std::ptr::null_mut(),
123    })
124}
125
126/// # Safety
127/// `input` must be a valid pointer to a null-terminated C string.
128#[no_mangle]
129pub unsafe extern "C" fn speechmarkdown_parse(input: *const c_char) -> *mut c_char {
130    if input.is_null() {
131        set_last_error("input argument is null");
132        return std::ptr::null_mut();
133    }
134
135    let input_str = match CStr::from_ptr(input).to_str() {
136        Ok(s) => s,
137        Err(_) => {
138            set_last_error("input is not valid UTF-8");
139            return std::ptr::null_mut();
140        }
141    };
142
143    match SpeechMarkdownParser::parse(input_str) {
144        Ok(ast) => match serde_json::to_string(&ast) {
145            Ok(json) => to_c_string(json),
146            Err(e) => {
147                set_last_error(&format!("JSON serialization error: {}", e));
148                std::ptr::null_mut()
149            }
150        },
151        Err(e) => {
152            set_last_error(&e.to_string());
153            std::ptr::null_mut()
154        }
155    }
156}