1use crate::stack::{Stack, pop, push};
22use crate::value::Value;
23use std::ffi::CStr;
24use std::io;
25use std::sync::LazyLock;
26
27static STDOUT_MUTEX: LazyLock<may::sync::Mutex<()>> = LazyLock::new(|| may::sync::Mutex::new(()));
32
33const EXIT_CODE_MIN: i64 = 0;
35const EXIT_CODE_MAX: i64 = 255;
36
37#[unsafe(no_mangle)]
49pub unsafe extern "C" fn patch_seq_write_line(stack: Stack) -> Stack {
50 assert!(!stack.is_null(), "write_line: stack is empty");
51
52 let (rest, value) = unsafe { pop(stack) };
53
54 match value {
55 Value::String(s) => {
56 let _guard = STDOUT_MUTEX.lock().unwrap();
59
60 let str_slice = s.as_str();
64 let newline = b"\n";
65 unsafe {
66 libc::write(
67 1,
68 str_slice.as_ptr() as *const libc::c_void,
69 str_slice.len(),
70 );
71 libc::write(1, newline.as_ptr() as *const libc::c_void, newline.len());
72 }
73
74 rest
75 }
76 _ => panic!("write_line: expected String on stack, got {:?}", value),
77 }
78}
79
80#[unsafe(no_mangle)]
91pub unsafe extern "C" fn patch_seq_read_line(stack: Stack) -> Stack {
92 use std::io::BufRead;
93
94 let stdin = io::stdin();
95 let mut line = String::new();
96
97 stdin
98 .lock()
99 .read_line(&mut line)
100 .expect("read_line: failed to read from stdin (I/O error or EOF)");
101
102 unsafe { push(stack, Value::String(line.into())) }
104}
105
106#[unsafe(no_mangle)]
119pub unsafe extern "C" fn patch_seq_read_line_plus(stack: Stack) -> Stack {
120 use std::io::BufRead;
121
122 let stdin = io::stdin();
123 let mut line = String::new();
124
125 let bytes_read = stdin
126 .lock()
127 .read_line(&mut line)
128 .expect("read_line_safe: failed to read from stdin");
129
130 let status = if bytes_read > 0 { 1i64 } else { 0i64 };
132
133 let stack = unsafe { push(stack, Value::String(line.into())) };
134 unsafe { push(stack, Value::Int(status)) }
135}
136
137#[unsafe(no_mangle)]
144pub unsafe extern "C" fn patch_seq_int_to_string(stack: Stack) -> Stack {
145 assert!(!stack.is_null(), "int_to_string: stack is empty");
146
147 let (rest, value) = unsafe { pop(stack) };
148
149 match value {
150 Value::Int(n) => unsafe { push(rest, Value::String(n.to_string().into())) },
151 _ => panic!("int_to_string: expected Int on stack, got {:?}", value),
152 }
153}
154
155#[unsafe(no_mangle)]
162pub unsafe extern "C" fn patch_seq_push_string(stack: Stack, c_str: *const i8) -> Stack {
163 assert!(!c_str.is_null(), "push_string: null string pointer");
164
165 let s = unsafe {
166 CStr::from_ptr(c_str)
167 .to_str()
168 .expect("push_string: invalid UTF-8 in string literal")
169 .to_owned()
170 };
171
172 unsafe { push(stack, Value::String(s.into())) }
173}
174
175#[allow(improper_ctypes_definitions)]
185#[unsafe(no_mangle)]
186pub unsafe extern "C" fn patch_seq_push_seqstring(
187 stack: Stack,
188 seq_str: crate::seqstring::SeqString,
189) -> Stack {
190 unsafe { push(stack, Value::String(seq_str)) }
191}
192
193#[unsafe(no_mangle)]
200pub unsafe extern "C" fn patch_seq_exit_op(stack: Stack) -> ! {
201 assert!(!stack.is_null(), "exit_op: stack is empty");
202
203 let (_rest, value) = unsafe { pop(stack) };
204
205 match value {
206 Value::Int(code) => {
207 if !(EXIT_CODE_MIN..=EXIT_CODE_MAX).contains(&code) {
209 panic!(
210 "exit_op: exit code must be in range {}-{}, got {}",
211 EXIT_CODE_MIN, EXIT_CODE_MAX, code
212 );
213 }
214 std::process::exit(code as i32);
215 }
216 _ => panic!("exit_op: expected Int on stack, got {:?}", value),
217 }
218}
219
220pub use patch_seq_exit_op as exit_op;
222pub use patch_seq_int_to_string as int_to_string;
223pub use patch_seq_push_seqstring as push_seqstring;
224pub use patch_seq_push_string as push_string;
225pub use patch_seq_read_line as read_line;
226pub use patch_seq_read_line_plus as read_line_plus;
227pub use patch_seq_write_line as write_line;
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::value::Value;
233 use std::ffi::CString;
234
235 #[test]
236 fn test_write_line() {
237 unsafe {
238 let stack = std::ptr::null_mut();
239 let stack = push(stack, Value::String("Hello, World!".into()));
240 let _stack = write_line(stack);
241 }
242 }
243
244 #[test]
245 fn test_push_string() {
246 unsafe {
247 let stack = std::ptr::null_mut();
248 let test_str = CString::new("Test").unwrap();
249 let stack = push_string(stack, test_str.as_ptr());
250
251 let (stack, value) = pop(stack);
252 assert_eq!(value, Value::String("Test".into()));
253 assert!(stack.is_null());
254 }
255 }
256
257 #[test]
258 fn test_empty_string() {
259 unsafe {
260 let stack = std::ptr::null_mut();
262 let empty_str = CString::new("").unwrap();
263 let stack = push_string(stack, empty_str.as_ptr());
264
265 let (stack, value) = pop(stack);
266 assert_eq!(value, Value::String("".into()));
267 assert!(stack.is_null());
268
269 let stack = push(stack, Value::String("".into()));
271 let stack = write_line(stack);
272 assert!(stack.is_null());
273 }
274 }
275
276 #[test]
277 fn test_unicode_strings() {
278 unsafe {
279 let stack = std::ptr::null_mut();
281 let unicode_str = CString::new("Hello, δΈη! π").unwrap();
282 let stack = push_string(stack, unicode_str.as_ptr());
283
284 let (stack, value) = pop(stack);
285 assert_eq!(value, Value::String("Hello, δΈη! π".into()));
286 assert!(stack.is_null());
287 }
288 }
289}