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 = std::ptr::null_mut();
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 assert!(stack.is_null());
282 }
283 }
284
285 #[test]
286 fn test_file_exists_true() {
287 let temp_file = NamedTempFile::new().unwrap();
288 let path = temp_file.path().to_str().unwrap().to_string();
289
290 unsafe {
291 let stack = std::ptr::null_mut();
292 let stack = push(stack, Value::String(path.into()));
293 let stack = patch_seq_file_exists(stack);
294
295 let (stack, value) = pop(stack);
296 assert_eq!(value, Value::Int(1));
297 assert!(stack.is_null());
298 }
299 }
300
301 #[test]
302 fn test_file_exists_false() {
303 unsafe {
304 let stack = std::ptr::null_mut();
305 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
306 let stack = patch_seq_file_exists(stack);
307
308 let (stack, value) = pop(stack);
309 assert_eq!(value, Value::Int(0));
310 assert!(stack.is_null());
311 }
312 }
313
314 #[test]
315 fn test_file_slurp_utf8() {
316 let mut temp_file = NamedTempFile::new().unwrap();
317 write!(temp_file, "Hello, δΈη! π").unwrap();
318 let path = temp_file.path().to_str().unwrap().to_string();
319
320 unsafe {
321 let stack = std::ptr::null_mut();
322 let stack = push(stack, Value::String(path.into()));
323 let stack = patch_seq_file_slurp(stack);
324
325 let (stack, value) = pop(stack);
326 match value {
327 Value::String(s) => assert_eq!(s.as_str(), "Hello, δΈη! π"),
328 _ => panic!("Expected String"),
329 }
330 assert!(stack.is_null());
331 }
332 }
333
334 #[test]
335 fn test_file_slurp_empty() {
336 let temp_file = NamedTempFile::new().unwrap();
337 let path = temp_file.path().to_str().unwrap().to_string();
338
339 unsafe {
340 let stack = std::ptr::null_mut();
341 let stack = push(stack, Value::String(path.into()));
342 let stack = patch_seq_file_slurp(stack);
343
344 let (stack, value) = pop(stack);
345 match value {
346 Value::String(s) => assert_eq!(s.as_str(), ""),
347 _ => panic!("Expected String"),
348 }
349 assert!(stack.is_null());
350 }
351 }
352
353 #[test]
354 fn test_file_slurp_safe_success() {
355 let mut temp_file = NamedTempFile::new().unwrap();
356 writeln!(temp_file, "Safe read!").unwrap();
357 let path = temp_file.path().to_str().unwrap().to_string();
358
359 unsafe {
360 let stack = std::ptr::null_mut();
361 let stack = push(stack, Value::String(path.into()));
362 let stack = patch_seq_file_slurp_safe(stack);
363
364 let (stack, success) = pop(stack);
365 let (stack, contents) = pop(stack);
366 assert_eq!(success, Value::Int(1));
367 match contents {
368 Value::String(s) => assert_eq!(s.as_str().trim(), "Safe read!"),
369 _ => panic!("Expected String"),
370 }
371 assert!(stack.is_null());
372 }
373 }
374
375 #[test]
376 fn test_file_slurp_safe_not_found() {
377 unsafe {
378 let stack = std::ptr::null_mut();
379 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
380 let stack = patch_seq_file_slurp_safe(stack);
381
382 let (stack, success) = pop(stack);
383 let (stack, contents) = pop(stack);
384 assert_eq!(success, Value::Int(0));
385 match contents {
386 Value::String(s) => assert_eq!(s.as_str(), ""),
387 _ => panic!("Expected String"),
388 }
389 assert!(stack.is_null());
390 }
391 }
392
393 #[test]
394 fn test_file_slurp_safe_empty_file() {
395 let temp_file = NamedTempFile::new().unwrap();
396 let path = temp_file.path().to_str().unwrap().to_string();
397
398 unsafe {
399 let stack = std::ptr::null_mut();
400 let stack = push(stack, Value::String(path.into()));
401 let stack = patch_seq_file_slurp_safe(stack);
402
403 let (stack, success) = pop(stack);
404 let (stack, contents) = pop(stack);
405 assert_eq!(success, Value::Int(1)); match contents {
407 Value::String(s) => assert_eq!(s.as_str(), ""),
408 _ => panic!("Expected String"),
409 }
410 assert!(stack.is_null());
411 }
412 }
413}