speechmarkdown_rust/
ffi.rs1use 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#[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#[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#[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#[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}
157
158#[no_mangle]
161pub unsafe extern "C" fn speechmarkdown_is_speech_markdown(input: *const c_char) -> bool {
162 if input.is_null() {
163 return false;
164 }
165
166 let input_str = match CStr::from_ptr(input).to_str() {
167 Ok(s) => s,
168 Err(_) => return false,
169 };
170
171 SpeechMarkdownParser::is_speech_markdown(input_str)
172}
173
174#[no_mangle]
177pub unsafe extern "C" fn speechmarkdown_validate(input: *const c_char) -> bool {
178 if input.is_null() {
179 set_last_error("input argument is null");
180 return false;
181 }
182
183 let input_str = match CStr::from_ptr(input).to_str() {
184 Ok(s) => s,
185 Err(_) => {
186 set_last_error("input is not valid UTF-8");
187 return false;
188 }
189 };
190
191 match SpeechMarkdownParser::validate(input_str) {
192 Ok(()) => true,
193 Err(e) => {
194 set_last_error(&e.to_string());
195 false
196 }
197 }
198}
199
200#[no_mangle]
203pub unsafe extern "C" fn speechmarkdown_to_smd(input: *const c_char) -> *mut c_char {
204 if input.is_null() {
205 set_last_error("input argument is null");
206 return std::ptr::null_mut();
207 }
208
209 let input_str = match CStr::from_ptr(input).to_str() {
210 Ok(s) => s,
211 Err(_) => {
212 set_last_error("input is not valid UTF-8");
213 return std::ptr::null_mut();
214 }
215 };
216
217 match SpeechMarkdownParser::to_smd(input_str) {
218 Ok(smd) => to_c_string(smd),
219 Err(e) => {
220 set_last_error(&e.to_string());
221 std::ptr::null_mut()
222 }
223 }
224}
225
226#[no_mangle]
229pub unsafe extern "C" fn speechmarkdown_supported_ssml(platform: *const c_char) -> *mut c_char {
230 if platform.is_null() {
231 set_last_error("platform argument is null");
232 return std::ptr::null_mut();
233 }
234
235 let platform_str = match CStr::from_ptr(platform).to_str() {
236 Ok(s) => s,
237 Err(_) => {
238 set_last_error("platform is not valid UTF-8");
239 return std::ptr::null_mut();
240 }
241 };
242
243 let platform_val = match Platform::from_platform_str(platform_str) {
244 Some(p) => p,
245 None => {
246 set_last_error(&format!("unsupported platform: '{}'", platform_str));
247 return std::ptr::null_mut();
248 }
249 };
250
251 let caps = SpeechMarkdownParser::supported_ssml(platform_val);
252 match serde_json::to_string(&caps) {
253 Ok(json) => to_c_string(json),
254 Err(e) => {
255 set_last_error(&format!("JSON serialization error: {}", e));
256 std::ptr::null_mut()
257 }
258 }
259}