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