use crate::stack::{Stack, pop, push};
use crate::value::Value;
use std::ffi::CStr;
use std::io;
use std::sync::LazyLock;
static STDOUT_MUTEX: LazyLock<may::sync::Mutex<()>> = LazyLock::new(|| may::sync::Mutex::new(()));
const EXIT_CODE_MIN: i64 = 0;
const EXIT_CODE_MAX: i64 = 255;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_write_line(stack: Stack) -> Stack {
assert!(!stack.is_null(), "write_line: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::String(s) => {
let _guard = STDOUT_MUTEX.lock().unwrap();
let str_slice = s.as_str();
let newline = b"\n";
unsafe {
libc::write(
1,
str_slice.as_ptr() as *const libc::c_void,
str_slice.len(),
);
libc::write(1, newline.as_ptr() as *const libc::c_void, newline.len());
}
rest
}
_ => panic!("write_line: expected String on stack, got {:?}", value),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_write(stack: Stack) -> Stack {
assert!(!stack.is_null(), "write: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::String(s) => {
let _guard = STDOUT_MUTEX.lock().unwrap();
let str_slice = s.as_str();
unsafe {
libc::write(
1,
str_slice.as_ptr() as *const libc::c_void,
str_slice.len(),
);
}
rest
}
_ => panic!("write: expected String on stack, got {:?}", value),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_read_line(stack: Stack) -> Stack {
use std::io::BufRead;
let stdin = io::stdin();
let mut line = String::new();
match stdin.lock().read_line(&mut line) {
Ok(0) => {
let stack = unsafe { push(stack, Value::String("".to_string().into())) };
unsafe { push(stack, Value::Bool(false)) }
}
Ok(_) => {
if line.ends_with("\r\n") {
line.pop(); line.pop(); line.push('\n'); }
let stack = unsafe { push(stack, Value::String(line.into())) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack = unsafe { push(stack, Value::String("".to_string().into())) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_read_line_plus(stack: Stack) -> Stack {
use std::io::BufRead;
let stdin = io::stdin();
let mut line = String::new();
match stdin.lock().read_line(&mut line) {
Ok(0) => {
let stack = unsafe { push(stack, Value::String("".to_string().into())) };
unsafe { push(stack, Value::Int(0)) }
}
Ok(_) => {
if line.ends_with("\r\n") {
line.pop(); line.pop(); line.push('\n'); }
let stack = unsafe { push(stack, Value::String(line.into())) };
unsafe { push(stack, Value::Int(1)) }
}
Err(_) => {
let stack = unsafe { push(stack, Value::String("".to_string().into())) };
unsafe { push(stack, Value::Int(0)) }
}
}
}
const READ_N_MAX_BYTES: i64 = 10 * 1024 * 1024;
fn validate_read_n_count(value: &Value) -> Result<usize, String> {
match value {
Value::Int(n) if *n < 0 => Err(format!(
"read_n: byte count must be non-negative, got {}",
n
)),
Value::Int(n) if *n > READ_N_MAX_BYTES => Err(format!(
"read_n: byte count {} exceeds maximum allowed ({})",
n, READ_N_MAX_BYTES
)),
Value::Int(n) => Ok(*n as usize),
_ => Err(format!("read_n: expected Int on stack, got {:?}", value)),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_read_n(stack: Stack) -> Stack {
use std::io::Read;
assert!(!stack.is_null(), "read_n: stack is empty");
let (stack, value) = unsafe { pop(stack) };
let n = match validate_read_n_count(&value) {
Ok(n) => n,
Err(_) => {
let stack = unsafe { push(stack, Value::String("".to_string().into())) };
return unsafe { push(stack, Value::Int(0)) };
}
};
let stdin = io::stdin();
let mut buffer = vec![0u8; n];
let mut total_read = 0;
{
let mut handle = stdin.lock();
while total_read < n {
match handle.read(&mut buffer[total_read..]) {
Ok(0) => break, Ok(bytes_read) => total_read += bytes_read,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(_) => break, }
}
}
buffer.truncate(total_read);
let s = String::from_utf8_lossy(&buffer).into_owned();
let status = if total_read == n { 1i64 } else { 0i64 };
let stack = unsafe { push(stack, Value::String(s.into())) };
unsafe { push(stack, Value::Int(status)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_int_to_string(stack: Stack) -> Stack {
assert!(!stack.is_null(), "int_to_string: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::Int(n) => unsafe { push(rest, Value::String(n.to_string().into())) },
_ => panic!("int_to_string: expected Int on stack, got {:?}", value),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_push_string(stack: Stack, c_str: *const i8) -> Stack {
assert!(!c_str.is_null(), "push_string: null string pointer");
let s = unsafe {
CStr::from_ptr(c_str)
.to_str()
.expect("push_string: invalid UTF-8 in string literal")
.to_owned()
};
unsafe { push(stack, Value::String(s.into())) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_push_symbol(stack: Stack, c_str: *const i8) -> Stack {
assert!(!c_str.is_null(), "push_symbol: null string pointer");
let s = unsafe {
CStr::from_ptr(c_str)
.to_str()
.expect("push_symbol: invalid UTF-8 in symbol literal")
.to_owned()
};
unsafe { push(stack, Value::Symbol(s.into())) }
}
#[repr(C)]
pub struct InternedSymbolData {
ptr: *const u8,
len: i64,
capacity: i64, global: i8, }
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_push_interned_symbol(
stack: Stack,
symbol_data: *const InternedSymbolData,
) -> Stack {
assert!(
!symbol_data.is_null(),
"push_interned_symbol: null symbol data pointer"
);
let data = unsafe { &*symbol_data };
assert!(!data.ptr.is_null(), "Interned symbol data pointer is null");
assert_eq!(data.capacity, 0, "Interned symbols must have capacity=0");
assert_ne!(data.global, 0, "Interned symbols must have global=1");
let seq_str = unsafe {
crate::seqstring::SeqString::from_raw_parts(
data.ptr,
data.len as usize,
data.capacity as usize, data.global != 0, )
};
unsafe { push(stack, Value::Symbol(seq_str)) }
}
#[allow(improper_ctypes_definitions)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_push_seqstring(
stack: Stack,
seq_str: crate::seqstring::SeqString,
) -> Stack {
unsafe { push(stack, Value::String(seq_str)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_symbol_to_string(stack: Stack) -> Stack {
assert!(!stack.is_null(), "symbol_to_string: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::Symbol(s) => unsafe { push(rest, Value::String(s)) },
_ => panic!(
"symbol_to_string: expected Symbol on stack, got {:?}",
value
),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_string_to_symbol(stack: Stack) -> Stack {
assert!(!stack.is_null(), "string_to_symbol: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::String(s) => unsafe { push(rest, Value::Symbol(s)) },
_ => panic!(
"string_to_symbol: expected String on stack, got {:?}",
value
),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_exit_op(stack: Stack) -> ! {
assert!(!stack.is_null(), "exit_op: stack is empty");
let (_rest, value) = unsafe { pop(stack) };
match value {
Value::Int(code) => {
if !(EXIT_CODE_MIN..=EXIT_CODE_MAX).contains(&code) {
panic!(
"exit_op: exit code must be in range {}-{}, got {}",
EXIT_CODE_MIN, EXIT_CODE_MAX, code
);
}
std::process::exit(code as i32);
}
_ => panic!("exit_op: expected Int on stack, got {:?}", value),
}
}
pub use patch_seq_exit_op as exit_op;
pub use patch_seq_int_to_string as int_to_string;
pub use patch_seq_push_interned_symbol as push_interned_symbol;
pub use patch_seq_push_seqstring as push_seqstring;
pub use patch_seq_push_string as push_string;
pub use patch_seq_push_symbol as push_symbol;
pub use patch_seq_read_line as read_line;
pub use patch_seq_read_line_plus as read_line_plus;
pub use patch_seq_read_n as read_n;
pub use patch_seq_string_to_symbol as string_to_symbol;
pub use patch_seq_symbol_to_string as symbol_to_string;
pub use patch_seq_write as write;
pub use patch_seq_write_line as write_line;
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Value;
use std::ffi::CString;
#[test]
fn test_write_line() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::String("Hello, World!".into()));
let _stack = write_line(stack);
}
}
#[test]
fn test_write() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::String("no newline".into()));
let _stack = write(stack);
}
}
#[test]
fn test_push_string() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let test_str = CString::new("Test").unwrap();
let stack = push_string(stack, test_str.as_ptr());
let (_stack, value) = pop(stack);
assert_eq!(value, Value::String("Test".into()));
}
}
#[test]
fn test_empty_string() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let empty_str = CString::new("").unwrap();
let stack = push_string(stack, empty_str.as_ptr());
let (_stack, value) = pop(stack);
assert_eq!(value, Value::String("".into()));
let stack = push(stack, Value::String("".into()));
let _stack = write_line(stack);
}
}
#[test]
fn test_unicode_strings() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let unicode_str = CString::new("Hello, δΈη! π").unwrap();
let stack = push_string(stack, unicode_str.as_ptr());
let (_stack, value) = pop(stack);
assert_eq!(value, Value::String("Hello, δΈη! π".into()));
}
}
#[test]
fn test_read_n_valid_input() {
assert_eq!(super::validate_read_n_count(&Value::Int(0)), Ok(0));
assert_eq!(super::validate_read_n_count(&Value::Int(100)), Ok(100));
assert_eq!(
super::validate_read_n_count(&Value::Int(1024 * 1024)), Ok(1024 * 1024)
);
}
#[test]
fn test_read_n_negative_input() {
let result = super::validate_read_n_count(&Value::Int(-1));
assert!(result.is_err());
assert!(result.unwrap_err().contains("must be non-negative"));
}
#[test]
fn test_read_n_large_negative_input() {
let result = super::validate_read_n_count(&Value::Int(i64::MIN));
assert!(result.is_err());
assert!(result.unwrap_err().contains("must be non-negative"));
}
#[test]
fn test_read_n_exceeds_max_bytes() {
let result = super::validate_read_n_count(&Value::Int(super::READ_N_MAX_BYTES + 1));
assert!(result.is_err());
assert!(result.unwrap_err().contains("exceeds maximum allowed"));
}
#[test]
fn test_read_n_at_max_bytes_ok() {
let result = super::validate_read_n_count(&Value::Int(super::READ_N_MAX_BYTES));
assert_eq!(result, Ok(super::READ_N_MAX_BYTES as usize));
}
#[test]
fn test_read_n_wrong_type_string() {
let result = super::validate_read_n_count(&Value::String("not an int".into()));
assert!(result.is_err());
assert!(result.unwrap_err().contains("expected Int"));
}
#[test]
fn test_read_n_wrong_type_bool() {
let result = super::validate_read_n_count(&Value::Bool(true));
assert!(result.is_err());
assert!(result.unwrap_err().contains("expected Int"));
}
}