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)]
94pub unsafe extern "C" fn patch_seq_write(stack: Stack) -> Stack {
95 assert!(!stack.is_null(), "write: stack is empty");
96
97 let (rest, value) = unsafe { pop(stack) };
98
99 match value {
100 Value::String(s) => {
101 let _guard = STDOUT_MUTEX.lock().unwrap();
102
103 let str_slice = s.as_str();
104 unsafe {
105 libc::write(
106 1,
107 str_slice.as_ptr() as *const libc::c_void,
108 str_slice.len(),
109 );
110 }
111
112 rest
113 }
114 _ => panic!("write: expected String on stack, got {:?}", value),
115 }
116}
117
118#[unsafe(no_mangle)]
139pub unsafe extern "C" fn patch_seq_read_line(stack: Stack) -> Stack {
140 use std::io::BufRead;
141
142 let stdin = io::stdin();
143 let mut line = String::new();
144
145 match stdin.lock().read_line(&mut line) {
146 Ok(0) => {
147 let stack = unsafe { push(stack, Value::String("".to_string().into())) };
149 unsafe { push(stack, Value::Bool(false)) }
150 }
151 Ok(_) => {
152 if line.ends_with("\r\n") {
154 line.pop(); line.pop(); line.push('\n'); }
158 let stack = unsafe { push(stack, Value::String(line.into())) };
159 unsafe { push(stack, Value::Bool(true)) }
160 }
161 Err(_) => {
162 let stack = unsafe { push(stack, Value::String("".to_string().into())) };
164 unsafe { push(stack, Value::Bool(false)) }
165 }
166 }
167}
168
169#[unsafe(no_mangle)]
189pub unsafe extern "C" fn patch_seq_read_line_plus(stack: Stack) -> Stack {
190 use std::io::BufRead;
191
192 let stdin = io::stdin();
193 let mut line = String::new();
194
195 match stdin.lock().read_line(&mut line) {
196 Ok(0) => {
197 let stack = unsafe { push(stack, Value::String("".to_string().into())) };
199 unsafe { push(stack, Value::Int(0)) }
200 }
201 Ok(_) => {
202 if line.ends_with("\r\n") {
204 line.pop(); line.pop(); line.push('\n'); }
208 let stack = unsafe { push(stack, Value::String(line.into())) };
209 unsafe { push(stack, Value::Int(1)) }
210 }
211 Err(_) => {
212 let stack = unsafe { push(stack, Value::String("".to_string().into())) };
214 unsafe { push(stack, Value::Int(0)) }
215 }
216 }
217}
218
219const READ_N_MAX_BYTES: i64 = 10 * 1024 * 1024;
223
224fn validate_read_n_count(value: &Value) -> Result<usize, String> {
227 match value {
228 Value::Int(n) if *n < 0 => Err(format!(
229 "read_n: byte count must be non-negative, got {}",
230 n
231 )),
232 Value::Int(n) if *n > READ_N_MAX_BYTES => Err(format!(
233 "read_n: byte count {} exceeds maximum allowed ({})",
234 n, READ_N_MAX_BYTES
235 )),
236 Value::Int(n) => Ok(*n as usize),
237 _ => Err(format!("read_n: expected Int on stack, got {:?}", value)),
238 }
239}
240
241#[unsafe(no_mangle)]
267pub unsafe extern "C" fn patch_seq_read_n(stack: Stack) -> Stack {
268 use std::io::Read;
269
270 assert!(!stack.is_null(), "read_n: stack is empty");
271
272 let (stack, value) = unsafe { pop(stack) };
273
274 let n = match validate_read_n_count(&value) {
276 Ok(n) => n,
277 Err(_) => {
278 let stack = unsafe { push(stack, Value::String("".to_string().into())) };
280 return unsafe { push(stack, Value::Int(0)) };
281 }
282 };
283
284 let stdin = io::stdin();
285 let mut buffer = vec![0u8; n];
286 let mut total_read = 0;
287
288 {
289 let mut handle = stdin.lock();
290 while total_read < n {
291 match handle.read(&mut buffer[total_read..]) {
292 Ok(0) => break, Ok(bytes_read) => total_read += bytes_read,
294 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
295 Err(_) => break, }
297 }
298 }
299
300 buffer.truncate(total_read);
302
303 let s = String::from_utf8_lossy(&buffer).into_owned();
305
306 let status = if total_read == n { 1i64 } else { 0i64 };
308
309 let stack = unsafe { push(stack, Value::String(s.into())) };
310 unsafe { push(stack, Value::Int(status)) }
311}
312
313#[unsafe(no_mangle)]
320pub unsafe extern "C" fn patch_seq_int_to_string(stack: Stack) -> Stack {
321 assert!(!stack.is_null(), "int_to_string: stack is empty");
322
323 let (rest, value) = unsafe { pop(stack) };
324
325 match value {
326 Value::Int(n) => unsafe { push(rest, Value::String(n.to_string().into())) },
327 _ => panic!("int_to_string: expected Int on stack, got {:?}", value),
328 }
329}
330
331#[unsafe(no_mangle)]
338pub unsafe extern "C" fn patch_seq_push_string(stack: Stack, c_str: *const i8) -> Stack {
339 assert!(!c_str.is_null(), "push_string: null string pointer");
340
341 let s = unsafe {
342 CStr::from_ptr(c_str)
343 .to_str()
344 .expect("push_string: invalid UTF-8 in string literal")
345 .to_owned()
346 };
347
348 unsafe { push(stack, Value::String(s.into())) }
349}
350
351#[unsafe(no_mangle)]
358pub unsafe extern "C" fn patch_seq_push_symbol(stack: Stack, c_str: *const i8) -> Stack {
359 assert!(!c_str.is_null(), "push_symbol: null string pointer");
360
361 let s = unsafe {
362 CStr::from_ptr(c_str)
363 .to_str()
364 .expect("push_symbol: invalid UTF-8 in symbol literal")
365 .to_owned()
366 };
367
368 unsafe { push(stack, Value::Symbol(s.into())) }
369}
370
371#[repr(C)]
388pub struct InternedSymbolData {
389 ptr: *const u8,
390 len: i64,
391 capacity: i64, global: i8, }
394
395#[unsafe(no_mangle)]
405pub unsafe extern "C" fn patch_seq_push_interned_symbol(
406 stack: Stack,
407 symbol_data: *const InternedSymbolData,
408) -> Stack {
409 assert!(
410 !symbol_data.is_null(),
411 "push_interned_symbol: null symbol data pointer"
412 );
413
414 let data = unsafe { &*symbol_data };
415
416 assert!(!data.ptr.is_null(), "Interned symbol data pointer is null");
419 assert_eq!(data.capacity, 0, "Interned symbols must have capacity=0");
420 assert_ne!(data.global, 0, "Interned symbols must have global=1");
421
422 let seq_str = unsafe {
427 crate::seqstring::SeqString::from_raw_parts(
428 data.ptr,
429 data.len as usize,
430 data.capacity as usize, data.global != 0, )
433 };
434
435 unsafe { push(stack, Value::Symbol(seq_str)) }
436}
437
438#[allow(improper_ctypes_definitions)]
448#[unsafe(no_mangle)]
449pub unsafe extern "C" fn patch_seq_push_seqstring(
450 stack: Stack,
451 seq_str: crate::seqstring::SeqString,
452) -> Stack {
453 unsafe { push(stack, Value::String(seq_str)) }
454}
455
456#[unsafe(no_mangle)]
463pub unsafe extern "C" fn patch_seq_symbol_to_string(stack: Stack) -> Stack {
464 assert!(!stack.is_null(), "symbol_to_string: stack is empty");
465
466 let (rest, value) = unsafe { pop(stack) };
467
468 match value {
469 Value::Symbol(s) => unsafe { push(rest, Value::String(s)) },
470 _ => panic!(
471 "symbol_to_string: expected Symbol on stack, got {:?}",
472 value
473 ),
474 }
475}
476
477#[unsafe(no_mangle)]
484pub unsafe extern "C" fn patch_seq_string_to_symbol(stack: Stack) -> Stack {
485 assert!(!stack.is_null(), "string_to_symbol: stack is empty");
486
487 let (rest, value) = unsafe { pop(stack) };
488
489 match value {
490 Value::String(s) => unsafe { push(rest, Value::Symbol(s)) },
491 _ => panic!(
492 "string_to_symbol: expected String on stack, got {:?}",
493 value
494 ),
495 }
496}
497
498#[unsafe(no_mangle)]
505pub unsafe extern "C" fn patch_seq_exit_op(stack: Stack) -> ! {
506 assert!(!stack.is_null(), "exit_op: stack is empty");
507
508 let (_rest, value) = unsafe { pop(stack) };
509
510 match value {
511 Value::Int(code) => {
512 if !(EXIT_CODE_MIN..=EXIT_CODE_MAX).contains(&code) {
514 panic!(
515 "exit_op: exit code must be in range {}-{}, got {}",
516 EXIT_CODE_MIN, EXIT_CODE_MAX, code
517 );
518 }
519 std::process::exit(code as i32);
520 }
521 _ => panic!("exit_op: expected Int on stack, got {:?}", value),
522 }
523}
524
525pub use patch_seq_exit_op as exit_op;
527pub use patch_seq_int_to_string as int_to_string;
528pub use patch_seq_push_interned_symbol as push_interned_symbol;
529pub use patch_seq_push_seqstring as push_seqstring;
530pub use patch_seq_push_string as push_string;
531pub use patch_seq_push_symbol as push_symbol;
532pub use patch_seq_read_line as read_line;
533pub use patch_seq_read_line_plus as read_line_plus;
534pub use patch_seq_read_n as read_n;
535pub use patch_seq_string_to_symbol as string_to_symbol;
536pub use patch_seq_symbol_to_string as symbol_to_string;
537pub use patch_seq_write as write;
538pub use patch_seq_write_line as write_line;
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543 use crate::value::Value;
544 use std::ffi::CString;
545
546 #[test]
547 fn test_write_line() {
548 unsafe {
549 let stack = crate::stack::alloc_test_stack();
550 let stack = push(stack, Value::String("Hello, World!".into()));
551 let _stack = write_line(stack);
552 }
553 }
554
555 #[test]
556 fn test_write() {
557 unsafe {
558 let stack = crate::stack::alloc_test_stack();
559 let stack = push(stack, Value::String("no newline".into()));
560 let _stack = write(stack);
561 }
562 }
563
564 #[test]
565 fn test_push_string() {
566 unsafe {
567 let stack = crate::stack::alloc_test_stack();
568 let test_str = CString::new("Test").unwrap();
569 let stack = push_string(stack, test_str.as_ptr());
570
571 let (_stack, value) = pop(stack);
572 assert_eq!(value, Value::String("Test".into()));
573 }
574 }
575
576 #[test]
577 fn test_empty_string() {
578 unsafe {
579 let stack = crate::stack::alloc_test_stack();
581 let empty_str = CString::new("").unwrap();
582 let stack = push_string(stack, empty_str.as_ptr());
583
584 let (_stack, value) = pop(stack);
585 assert_eq!(value, Value::String("".into()));
586
587 let stack = push(stack, Value::String("".into()));
589 let _stack = write_line(stack);
590 }
591 }
592
593 #[test]
594 fn test_unicode_strings() {
595 unsafe {
596 let stack = crate::stack::alloc_test_stack();
598 let unicode_str = CString::new("Hello, δΈη! π").unwrap();
599 let stack = push_string(stack, unicode_str.as_ptr());
600
601 let (_stack, value) = pop(stack);
602 assert_eq!(value, Value::String("Hello, δΈη! π".into()));
603 }
604 }
605
606 #[test]
611 fn test_read_n_valid_input() {
612 assert_eq!(super::validate_read_n_count(&Value::Int(0)), Ok(0));
613 assert_eq!(super::validate_read_n_count(&Value::Int(100)), Ok(100));
614 assert_eq!(
615 super::validate_read_n_count(&Value::Int(1024 * 1024)), Ok(1024 * 1024)
617 );
618 }
619
620 #[test]
621 fn test_read_n_negative_input() {
622 let result = super::validate_read_n_count(&Value::Int(-1));
623 assert!(result.is_err());
624 assert!(result.unwrap_err().contains("must be non-negative"));
625 }
626
627 #[test]
628 fn test_read_n_large_negative_input() {
629 let result = super::validate_read_n_count(&Value::Int(i64::MIN));
630 assert!(result.is_err());
631 assert!(result.unwrap_err().contains("must be non-negative"));
632 }
633
634 #[test]
635 fn test_read_n_exceeds_max_bytes() {
636 let result = super::validate_read_n_count(&Value::Int(super::READ_N_MAX_BYTES + 1));
637 assert!(result.is_err());
638 assert!(result.unwrap_err().contains("exceeds maximum allowed"));
639 }
640
641 #[test]
642 fn test_read_n_at_max_bytes_ok() {
643 let result = super::validate_read_n_count(&Value::Int(super::READ_N_MAX_BYTES));
645 assert_eq!(result, Ok(super::READ_N_MAX_BYTES as usize));
646 }
647
648 #[test]
649 fn test_read_n_wrong_type_string() {
650 let result = super::validate_read_n_count(&Value::String("not an int".into()));
651 assert!(result.is_err());
652 assert!(result.unwrap_err().contains("expected Int"));
653 }
654
655 #[test]
656 fn test_read_n_wrong_type_bool() {
657 let result = super::validate_read_n_count(&Value::Bool(true));
658 assert!(result.is_err());
659 assert!(result.unwrap_err().contains("expected Int"));
660 }
661}