1use crate::stack::{Stack, pop, push};
27use crate::value::{Value, VariantData};
28use std::fs::{self, File, OpenOptions};
29use std::io::{BufRead, BufReader, Write};
30use std::path::Path;
31use std::sync::Arc;
32
33#[unsafe(no_mangle)]
46pub unsafe extern "C" fn patch_seq_file_slurp(stack: Stack) -> Stack {
47 assert!(!stack.is_null(), "file-slurp: stack is empty");
48
49 let (rest, value) = unsafe { pop(stack) };
50
51 match value {
52 Value::String(path) => match fs::read_to_string(path.as_str()) {
53 Ok(contents) => {
54 let stack = unsafe { push(rest, Value::String(contents.into())) };
55 unsafe { push(stack, Value::Bool(true)) }
56 }
57 Err(_) => {
58 let stack = unsafe { push(rest, Value::String("".into())) };
59 unsafe { push(stack, Value::Bool(false)) }
60 }
61 },
62 _ => panic!("file-slurp: expected String path on stack, got {:?}", value),
63 }
64}
65
66#[unsafe(no_mangle)]
76pub unsafe extern "C" fn patch_seq_file_exists(stack: Stack) -> Stack {
77 assert!(!stack.is_null(), "file-exists?: stack is empty");
78
79 let (rest, value) = unsafe { pop(stack) };
80
81 match value {
82 Value::String(path) => {
83 let exists = Path::new(path.as_str()).exists();
84 unsafe { push(rest, Value::Bool(exists)) }
85 }
86 _ => panic!(
87 "file-exists?: expected String path on stack, got {:?}",
88 value
89 ),
90 }
91}
92
93#[unsafe(no_mangle)]
129pub unsafe extern "C" fn patch_seq_file_for_each_line_plus(stack: Stack) -> Stack {
130 assert!(!stack.is_null(), "file-for-each-line+: stack is empty");
131
132 let (stack, quot_value) = unsafe { pop(stack) };
134
135 let (stack, path_value) = unsafe { pop(stack) };
137 let path = match path_value {
138 Value::String(s) => s,
139 _ => panic!(
140 "file-for-each-line+: expected String path, got {:?}",
141 path_value
142 ),
143 };
144
145 let file = match File::open(path.as_str()) {
147 Ok(f) => f,
148 Err(e) => {
149 let stack = unsafe { push(stack, Value::String(e.to_string().into())) };
151 return unsafe { push(stack, Value::Int(0)) };
152 }
153 };
154
155 let (wrapper, env_data, env_len): (usize, *const Value, usize) = match quot_value {
157 Value::Quotation { wrapper, .. } => {
158 if wrapper == 0 {
159 panic!("file-for-each-line+: quotation wrapper function pointer is null");
160 }
161 (wrapper, std::ptr::null(), 0)
162 }
163 Value::Closure { fn_ptr, ref env } => {
164 if fn_ptr == 0 {
165 panic!("file-for-each-line+: closure function pointer is null");
166 }
167 (fn_ptr, env.as_ptr(), env.len())
168 }
169 _ => panic!(
170 "file-for-each-line+: expected Quotation or Closure, got {:?}",
171 quot_value
172 ),
173 };
174
175 let reader = BufReader::new(file);
177 let mut current_stack = stack;
178
179 for line_result in reader.lines() {
180 match line_result {
181 Ok(mut line_str) => {
182 line_str.push('\n');
185
186 current_stack = unsafe { push(current_stack, Value::String(line_str.into())) };
188
189 if env_data.is_null() {
191 let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
193 unsafe { std::mem::transmute(wrapper) };
194 current_stack = unsafe { fn_ref(current_stack) };
195 } else {
196 let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
198 unsafe { std::mem::transmute(wrapper) };
199 current_stack = unsafe { fn_ref(current_stack, env_data, env_len) };
200 }
201
202 may::coroutine::yield_now();
204 }
205 Err(e) => {
206 let stack = unsafe { push(current_stack, Value::String(e.to_string().into())) };
208 return unsafe { push(stack, Value::Bool(false)) };
209 }
210 }
211 }
212
213 let stack = unsafe { push(current_stack, Value::String("".into())) };
215 unsafe { push(stack, Value::Bool(true)) }
216}
217
218#[unsafe(no_mangle)]
230pub unsafe extern "C" fn patch_seq_file_spit(stack: Stack) -> Stack {
231 assert!(!stack.is_null(), "file.spit: stack is empty");
232
233 let (stack, path_value) = unsafe { pop(stack) };
235 let path = match path_value {
236 Value::String(s) => s,
237 _ => panic!("file.spit: expected String path, got {:?}", path_value),
238 };
239
240 let (stack, content_value) = unsafe { pop(stack) };
242 let content = match content_value {
243 Value::String(s) => s,
244 _ => panic!(
245 "file.spit: expected String content, got {:?}",
246 content_value
247 ),
248 };
249
250 match fs::write(path.as_str(), content.as_str()) {
251 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
252 Err(_) => unsafe { push(stack, Value::Bool(false)) },
253 }
254}
255
256#[unsafe(no_mangle)]
268pub unsafe extern "C" fn patch_seq_file_append(stack: Stack) -> Stack {
269 assert!(!stack.is_null(), "file.append: stack is empty");
270
271 let (stack, path_value) = unsafe { pop(stack) };
273 let path = match path_value {
274 Value::String(s) => s,
275 _ => panic!("file.append: expected String path, got {:?}", path_value),
276 };
277
278 let (stack, content_value) = unsafe { pop(stack) };
280 let content = match content_value {
281 Value::String(s) => s,
282 _ => panic!(
283 "file.append: expected String content, got {:?}",
284 content_value
285 ),
286 };
287
288 let result = OpenOptions::new()
289 .create(true)
290 .append(true)
291 .open(path.as_str())
292 .and_then(|mut file| file.write_all(content.as_str().as_bytes()));
293
294 match result {
295 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
296 Err(_) => unsafe { push(stack, Value::Bool(false)) },
297 }
298}
299
300#[unsafe(no_mangle)]
311pub unsafe extern "C" fn patch_seq_file_delete(stack: Stack) -> Stack {
312 assert!(!stack.is_null(), "file.delete: stack is empty");
313
314 let (stack, path_value) = unsafe { pop(stack) };
315 let path = match path_value {
316 Value::String(s) => s,
317 _ => panic!("file.delete: expected String path, got {:?}", path_value),
318 };
319
320 match fs::remove_file(path.as_str()) {
321 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
322 Err(_) => unsafe { push(stack, Value::Bool(false)) },
323 }
324}
325
326#[unsafe(no_mangle)]
337pub unsafe extern "C" fn patch_seq_file_size(stack: Stack) -> Stack {
338 assert!(!stack.is_null(), "file.size: stack is empty");
339
340 let (stack, path_value) = unsafe { pop(stack) };
341 let path = match path_value {
342 Value::String(s) => s,
343 _ => panic!("file.size: expected String path, got {:?}", path_value),
344 };
345
346 match fs::metadata(path.as_str()) {
347 Ok(metadata) => {
348 let size = metadata.len() as i64;
349 let stack = unsafe { push(stack, Value::Int(size)) };
350 unsafe { push(stack, Value::Bool(true)) }
351 }
352 Err(_) => {
353 let stack = unsafe { push(stack, Value::Int(0)) };
354 unsafe { push(stack, Value::Bool(false)) }
355 }
356 }
357}
358
359#[unsafe(no_mangle)]
373pub unsafe extern "C" fn patch_seq_dir_exists(stack: Stack) -> Stack {
374 assert!(!stack.is_null(), "dir.exists?: stack is empty");
375
376 let (stack, path_value) = unsafe { pop(stack) };
377 let path = match path_value {
378 Value::String(s) => s,
379 _ => panic!("dir.exists?: expected String path, got {:?}", path_value),
380 };
381
382 let exists = Path::new(path.as_str()).is_dir();
383 unsafe { push(stack, Value::Bool(exists)) }
384}
385
386#[unsafe(no_mangle)]
397pub unsafe extern "C" fn patch_seq_dir_make(stack: Stack) -> Stack {
398 assert!(!stack.is_null(), "dir.make: stack is empty");
399
400 let (stack, path_value) = unsafe { pop(stack) };
401 let path = match path_value {
402 Value::String(s) => s,
403 _ => panic!("dir.make: expected String path, got {:?}", path_value),
404 };
405
406 match fs::create_dir_all(path.as_str()) {
407 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
408 Err(_) => unsafe { push(stack, Value::Bool(false)) },
409 }
410}
411
412#[unsafe(no_mangle)]
423pub unsafe extern "C" fn patch_seq_dir_delete(stack: Stack) -> Stack {
424 assert!(!stack.is_null(), "dir.delete: stack is empty");
425
426 let (stack, path_value) = unsafe { pop(stack) };
427 let path = match path_value {
428 Value::String(s) => s,
429 _ => panic!("dir.delete: expected String path, got {:?}", path_value),
430 };
431
432 match fs::remove_dir(path.as_str()) {
433 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
434 Err(_) => unsafe { push(stack, Value::Bool(false)) },
435 }
436}
437
438#[unsafe(no_mangle)]
449pub unsafe extern "C" fn patch_seq_dir_list(stack: Stack) -> Stack {
450 assert!(!stack.is_null(), "dir.list: stack is empty");
451
452 let (stack, path_value) = unsafe { pop(stack) };
453 let path = match path_value {
454 Value::String(s) => s,
455 _ => panic!("dir.list: expected String path, got {:?}", path_value),
456 };
457
458 match fs::read_dir(path.as_str()) {
459 Ok(entries) => {
460 let mut names: Vec<Value> = Vec::new();
461 for entry in entries.flatten() {
462 if let Some(name) = entry.file_name().to_str() {
463 names.push(Value::String(name.to_string().into()));
464 }
465 }
466 let list = Value::Variant(Arc::new(VariantData::new(
467 crate::seqstring::global_string("List".to_string()),
468 names,
469 )));
470 let stack = unsafe { push(stack, list) };
471 unsafe { push(stack, Value::Bool(true)) }
472 }
473 Err(_) => {
474 let empty_list = Value::Variant(Arc::new(VariantData::new(
475 crate::seqstring::global_string("List".to_string()),
476 vec![],
477 )));
478 let stack = unsafe { push(stack, empty_list) };
479 unsafe { push(stack, Value::Bool(false)) }
480 }
481 }
482}
483
484pub use patch_seq_dir_delete as dir_delete;
486pub use patch_seq_dir_exists as dir_exists;
487pub use patch_seq_dir_list as dir_list;
488pub use patch_seq_dir_make as dir_make;
489pub use patch_seq_file_append as file_append;
490pub use patch_seq_file_delete as file_delete;
491pub use patch_seq_file_exists as file_exists;
492pub use patch_seq_file_for_each_line_plus as file_for_each_line_plus;
493pub use patch_seq_file_size as file_size;
494pub use patch_seq_file_slurp as file_slurp;
495pub use patch_seq_file_spit as file_spit;
496
497#[cfg(test)]
498mod tests;