1use crate::stack::{Stack, pop, push};
27use crate::value::Value;
28use std::fs::{self, File};
29use std::io::{BufRead, BufReader};
30use std::path::Path;
31
32#[unsafe(no_mangle)]
43pub unsafe extern "C" fn patch_seq_file_slurp(stack: Stack) -> Stack {
44 assert!(!stack.is_null(), "file-slurp: stack is empty");
45
46 let (rest, value) = unsafe { pop(stack) };
47
48 match value {
49 Value::String(path) => {
50 let contents = fs::read_to_string(path.as_str()).unwrap_or_else(|e| {
51 panic!("file-slurp: failed to read '{}': {}", path.as_str(), e)
52 });
53
54 unsafe { push(rest, Value::String(contents.into())) }
55 }
56 _ => panic!("file-slurp: expected String path on stack, got {:?}", value),
57 }
58}
59
60#[unsafe(no_mangle)]
70pub unsafe extern "C" fn patch_seq_file_exists(stack: Stack) -> Stack {
71 assert!(!stack.is_null(), "file-exists?: stack is empty");
72
73 let (rest, value) = unsafe { pop(stack) };
74
75 match value {
76 Value::String(path) => {
77 let exists = if Path::new(path.as_str()).exists() {
78 1i64
79 } else {
80 0i64
81 };
82
83 unsafe { push(rest, Value::Int(exists)) }
84 }
85 _ => panic!(
86 "file-exists?: expected String path on stack, got {:?}",
87 value
88 ),
89 }
90}
91
92#[unsafe(no_mangle)]
104pub unsafe extern "C" fn patch_seq_file_slurp_safe(stack: Stack) -> Stack {
105 assert!(!stack.is_null(), "file-slurp-safe: stack is empty");
106
107 let (rest, value) = unsafe { pop(stack) };
108
109 match value {
110 Value::String(path) => match fs::read_to_string(path.as_str()) {
111 Ok(contents) => {
112 let stack = unsafe { push(rest, Value::String(contents.into())) };
113 unsafe { push(stack, Value::Int(1)) }
114 }
115 Err(_) => {
116 let stack = unsafe { push(rest, Value::String("".into())) };
117 unsafe { push(stack, Value::Int(0)) }
118 }
119 },
120 _ => panic!(
121 "file-slurp-safe: expected String path on stack, got {:?}",
122 value
123 ),
124 }
125}
126
127#[unsafe(no_mangle)]
163pub unsafe extern "C" fn patch_seq_file_for_each_line_plus(stack: Stack) -> Stack {
164 assert!(!stack.is_null(), "file-for-each-line+: stack is empty");
165
166 let (stack, quot_value) = unsafe { pop(stack) };
168
169 let (stack, path_value) = unsafe { pop(stack) };
171 let path = match path_value {
172 Value::String(s) => s,
173 _ => panic!(
174 "file-for-each-line+: expected String path, got {:?}",
175 path_value
176 ),
177 };
178
179 let file = match File::open(path.as_str()) {
181 Ok(f) => f,
182 Err(e) => {
183 let stack = unsafe { push(stack, Value::String(e.to_string().into())) };
185 return unsafe { push(stack, Value::Int(0)) };
186 }
187 };
188
189 let (wrapper, env_data, env_len): (usize, *const Value, usize) = match quot_value {
191 Value::Quotation { wrapper, .. } => {
192 if wrapper == 0 {
193 panic!("file-for-each-line+: quotation wrapper function pointer is null");
194 }
195 (wrapper, std::ptr::null(), 0)
196 }
197 Value::Closure { fn_ptr, ref env } => {
198 if fn_ptr == 0 {
199 panic!("file-for-each-line+: closure function pointer is null");
200 }
201 (fn_ptr, env.as_ptr(), env.len())
202 }
203 _ => panic!(
204 "file-for-each-line+: expected Quotation or Closure, got {:?}",
205 quot_value
206 ),
207 };
208
209 let reader = BufReader::new(file);
211 let mut current_stack = stack;
212
213 for line_result in reader.lines() {
214 match line_result {
215 Ok(mut line_str) => {
216 line_str.push('\n');
219
220 current_stack = unsafe { push(current_stack, Value::String(line_str.into())) };
222
223 if env_data.is_null() {
225 let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
227 unsafe { std::mem::transmute(wrapper) };
228 current_stack = unsafe { fn_ref(current_stack) };
229 } else {
230 let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
232 unsafe { std::mem::transmute(wrapper) };
233 current_stack = unsafe { fn_ref(current_stack, env_data, env_len) };
234 }
235
236 may::coroutine::yield_now();
238 }
239 Err(e) => {
240 let stack = unsafe { push(current_stack, Value::String(e.to_string().into())) };
242 return unsafe { push(stack, Value::Int(0)) };
243 }
244 }
245 }
246
247 let stack = unsafe { push(current_stack, Value::String("".into())) };
249 unsafe { push(stack, Value::Int(1)) }
250}
251
252pub use patch_seq_file_exists as file_exists;
254pub use patch_seq_file_for_each_line_plus as file_for_each_line_plus;
255pub use patch_seq_file_slurp as file_slurp;
256pub use patch_seq_file_slurp_safe as file_slurp_safe;
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use std::io::Write;
262 use tempfile::NamedTempFile;
263
264 #[test]
265 fn test_file_slurp() {
266 let mut temp_file = NamedTempFile::new().unwrap();
268 writeln!(temp_file, "Hello, file!").unwrap();
269 let path = temp_file.path().to_str().unwrap().to_string();
270
271 unsafe {
272 let stack = crate::stack::alloc_test_stack();
273 let stack = push(stack, Value::String(path.into()));
274 let stack = patch_seq_file_slurp(stack);
275
276 let (_stack, value) = pop(stack);
277 match value {
278 Value::String(s) => assert_eq!(s.as_str().trim(), "Hello, file!"),
279 _ => panic!("Expected String"),
280 }
281 }
282 }
283
284 #[test]
285 fn test_file_exists_true() {
286 let temp_file = NamedTempFile::new().unwrap();
287 let path = temp_file.path().to_str().unwrap().to_string();
288
289 unsafe {
290 let stack = crate::stack::alloc_test_stack();
291 let stack = push(stack, Value::String(path.into()));
292 let stack = patch_seq_file_exists(stack);
293
294 let (_stack, value) = pop(stack);
295 assert_eq!(value, Value::Int(1));
296 }
297 }
298
299 #[test]
300 fn test_file_exists_false() {
301 unsafe {
302 let stack = crate::stack::alloc_test_stack();
303 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
304 let stack = patch_seq_file_exists(stack);
305
306 let (_stack, value) = pop(stack);
307 assert_eq!(value, Value::Int(0));
308 }
309 }
310
311 #[test]
312 fn test_file_slurp_utf8() {
313 let mut temp_file = NamedTempFile::new().unwrap();
314 write!(temp_file, "Hello, δΈη! π").unwrap();
315 let path = temp_file.path().to_str().unwrap().to_string();
316
317 unsafe {
318 let stack = crate::stack::alloc_test_stack();
319 let stack = push(stack, Value::String(path.into()));
320 let stack = patch_seq_file_slurp(stack);
321
322 let (_stack, value) = pop(stack);
323 match value {
324 Value::String(s) => assert_eq!(s.as_str(), "Hello, δΈη! π"),
325 _ => panic!("Expected String"),
326 }
327 }
328 }
329
330 #[test]
331 fn test_file_slurp_empty() {
332 let temp_file = NamedTempFile::new().unwrap();
333 let path = temp_file.path().to_str().unwrap().to_string();
334
335 unsafe {
336 let stack = crate::stack::alloc_test_stack();
337 let stack = push(stack, Value::String(path.into()));
338 let stack = patch_seq_file_slurp(stack);
339
340 let (_stack, value) = pop(stack);
341 match value {
342 Value::String(s) => assert_eq!(s.as_str(), ""),
343 _ => panic!("Expected String"),
344 }
345 }
346 }
347
348 #[test]
349 fn test_file_slurp_safe_success() {
350 let mut temp_file = NamedTempFile::new().unwrap();
351 writeln!(temp_file, "Safe read!").unwrap();
352 let path = temp_file.path().to_str().unwrap().to_string();
353
354 unsafe {
355 let stack = crate::stack::alloc_test_stack();
356 let stack = push(stack, Value::String(path.into()));
357 let stack = patch_seq_file_slurp_safe(stack);
358
359 let (stack, success) = pop(stack);
360 let (_stack, contents) = pop(stack);
361 assert_eq!(success, Value::Int(1));
362 match contents {
363 Value::String(s) => assert_eq!(s.as_str().trim(), "Safe read!"),
364 _ => panic!("Expected String"),
365 }
366 }
367 }
368
369 #[test]
370 fn test_file_slurp_safe_not_found() {
371 unsafe {
372 let stack = crate::stack::alloc_test_stack();
373 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
374 let stack = patch_seq_file_slurp_safe(stack);
375
376 let (stack, success) = pop(stack);
377 let (_stack, contents) = pop(stack);
378 assert_eq!(success, Value::Int(0));
379 match contents {
380 Value::String(s) => assert_eq!(s.as_str(), ""),
381 _ => panic!("Expected String"),
382 }
383 }
384 }
385
386 #[test]
387 fn test_file_slurp_safe_empty_file() {
388 let temp_file = NamedTempFile::new().unwrap();
389 let path = temp_file.path().to_str().unwrap().to_string();
390
391 unsafe {
392 let stack = crate::stack::alloc_test_stack();
393 let stack = push(stack, Value::String(path.into()));
394 let stack = patch_seq_file_slurp_safe(stack);
395
396 let (stack, success) = pop(stack);
397 let (_stack, contents) = pop(stack);
398 assert_eq!(success, Value::Int(1)); match contents {
400 Value::String(s) => assert_eq!(s.as_str(), ""),
401 _ => panic!("Expected String"),
402 }
403 }
404 }
405}