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)]
87pub unsafe extern "C" fn patch_seq_read_line(stack: Stack) -> Stack {
88 use std::io::BufRead;
89
90 let stdin = io::stdin();
91 let mut line = String::new();
92
93 stdin
94 .lock()
95 .read_line(&mut line)
96 .expect("read_line: failed to read from stdin (I/O error or EOF)");
97
98 if line.ends_with('\n') {
100 line.pop();
101 if line.ends_with('\r') {
102 line.pop();
103 }
104 }
105
106 unsafe { push(stack, Value::String(line.into())) }
107}
108
109#[unsafe(no_mangle)]
116pub unsafe extern "C" fn patch_seq_int_to_string(stack: Stack) -> Stack {
117 assert!(!stack.is_null(), "int_to_string: stack is empty");
118
119 let (rest, value) = unsafe { pop(stack) };
120
121 match value {
122 Value::Int(n) => unsafe { push(rest, Value::String(n.to_string().into())) },
123 _ => panic!("int_to_string: expected Int on stack, got {:?}", value),
124 }
125}
126
127#[unsafe(no_mangle)]
134pub unsafe extern "C" fn patch_seq_push_string(stack: Stack, c_str: *const i8) -> Stack {
135 assert!(!c_str.is_null(), "push_string: null string pointer");
136
137 let s = unsafe {
138 CStr::from_ptr(c_str)
139 .to_str()
140 .expect("push_string: invalid UTF-8 in string literal")
141 .to_owned()
142 };
143
144 unsafe { push(stack, Value::String(s.into())) }
145}
146
147#[allow(improper_ctypes_definitions)]
157#[unsafe(no_mangle)]
158pub unsafe extern "C" fn patch_seq_push_seqstring(
159 stack: Stack,
160 seq_str: crate::seqstring::SeqString,
161) -> Stack {
162 unsafe { push(stack, Value::String(seq_str)) }
163}
164
165#[unsafe(no_mangle)]
172pub unsafe extern "C" fn patch_seq_exit_op(stack: Stack) -> ! {
173 assert!(!stack.is_null(), "exit_op: stack is empty");
174
175 let (_rest, value) = unsafe { pop(stack) };
176
177 match value {
178 Value::Int(code) => {
179 if !(EXIT_CODE_MIN..=EXIT_CODE_MAX).contains(&code) {
181 panic!(
182 "exit_op: exit code must be in range {}-{}, got {}",
183 EXIT_CODE_MIN, EXIT_CODE_MAX, code
184 );
185 }
186 std::process::exit(code as i32);
187 }
188 _ => panic!("exit_op: expected Int on stack, got {:?}", value),
189 }
190}
191
192pub use patch_seq_exit_op as exit_op;
194pub use patch_seq_int_to_string as int_to_string;
195pub use patch_seq_push_seqstring as push_seqstring;
196pub use patch_seq_push_string as push_string;
197pub use patch_seq_read_line as read_line;
198pub use patch_seq_write_line as write_line;
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::value::Value;
204 use std::ffi::CString;
205
206 #[test]
207 fn test_write_line() {
208 unsafe {
209 let stack = std::ptr::null_mut();
210 let stack = push(stack, Value::String("Hello, World!".into()));
211 let _stack = write_line(stack);
212 }
213 }
214
215 #[test]
216 fn test_push_string() {
217 unsafe {
218 let stack = std::ptr::null_mut();
219 let test_str = CString::new("Test").unwrap();
220 let stack = push_string(stack, test_str.as_ptr());
221
222 let (stack, value) = pop(stack);
223 assert_eq!(value, Value::String("Test".into()));
224 assert!(stack.is_null());
225 }
226 }
227
228 #[test]
229 fn test_empty_string() {
230 unsafe {
231 let stack = std::ptr::null_mut();
233 let empty_str = CString::new("").unwrap();
234 let stack = push_string(stack, empty_str.as_ptr());
235
236 let (stack, value) = pop(stack);
237 assert_eq!(value, Value::String("".into()));
238 assert!(stack.is_null());
239
240 let stack = push(stack, Value::String("".into()));
242 let stack = write_line(stack);
243 assert!(stack.is_null());
244 }
245 }
246
247 #[test]
248 fn test_unicode_strings() {
249 unsafe {
250 let stack = std::ptr::null_mut();
252 let unicode_str = CString::new("Hello, δΈη! π").unwrap();
253 let stack = push_string(stack, unicode_str.as_ptr());
254
255 let (stack, value) = pop(stack);
256 assert_eq!(value, Value::String("Hello, δΈη! π".into()));
257 assert!(stack.is_null());
258 }
259 }
260}