1use crate::seqstring::global_bytes;
27use crate::stack::{Stack, pop, push};
28use crate::value::{Value, VariantData};
29use std::fs::{self, File, OpenOptions};
30use std::io::{BufRead, BufReader, Write};
31use std::path::Path;
32use std::sync::Arc;
33
34fn path_str(s: &crate::seqstring::SeqString) -> &str {
42 s.as_str_or_empty()
43}
44
45#[unsafe(no_mangle)]
58pub unsafe extern "C" fn patch_seq_file_slurp(stack: Stack) -> Stack {
59 assert!(!stack.is_null(), "file-slurp: stack is empty");
60
61 let (rest, value) = unsafe { pop(stack) };
62
63 match value {
64 Value::String(path) => match fs::read(path_str(&path)) {
68 Ok(contents) => {
69 let stack = unsafe { push(rest, Value::String(global_bytes(contents))) };
70 unsafe { push(stack, Value::Bool(true)) }
71 }
72 Err(_) => {
73 let stack = unsafe { push(rest, Value::String("".into())) };
74 unsafe { push(stack, Value::Bool(false)) }
75 }
76 },
77 _ => panic!("file-slurp: expected String path on stack, got {:?}", value),
78 }
79}
80
81#[unsafe(no_mangle)]
91pub unsafe extern "C" fn patch_seq_file_exists(stack: Stack) -> Stack {
92 assert!(!stack.is_null(), "file-exists?: stack is empty");
93
94 let (rest, value) = unsafe { pop(stack) };
95
96 match value {
97 Value::String(path) => {
98 let exists = Path::new(path_str(&path)).exists();
99 unsafe { push(rest, Value::Bool(exists)) }
100 }
101 _ => panic!(
102 "file-exists?: expected String path on stack, got {:?}",
103 value
104 ),
105 }
106}
107
108#[unsafe(no_mangle)]
144pub unsafe extern "C" fn patch_seq_file_for_each_line_plus(stack: Stack) -> Stack {
145 assert!(!stack.is_null(), "file-for-each-line+: stack is empty");
146
147 let (stack, quot_value) = unsafe { pop(stack) };
149
150 let (stack, path_value) = unsafe { pop(stack) };
152 let path = match path_value {
153 Value::String(s) => s,
154 _ => panic!(
155 "file-for-each-line+: expected String path, got {:?}",
156 path_value
157 ),
158 };
159
160 let file = match File::open(path_str(&path)) {
162 Ok(f) => f,
163 Err(e) => {
164 let stack = unsafe { push(stack, Value::String(e.to_string().into())) };
166 return unsafe { push(stack, Value::Int(0)) };
167 }
168 };
169
170 let (wrapper, env_data, env_len): (usize, *const Value, usize) = match quot_value {
172 Value::Quotation { wrapper, .. } => {
173 if wrapper == 0 {
174 panic!("file-for-each-line+: quotation wrapper function pointer is null");
175 }
176 (wrapper, std::ptr::null(), 0)
177 }
178 Value::Closure { fn_ptr, ref env } => {
179 if fn_ptr == 0 {
180 panic!("file-for-each-line+: closure function pointer is null");
181 }
182 (fn_ptr, env.as_ptr(), env.len())
183 }
184 _ => panic!(
185 "file-for-each-line+: expected Quotation or Closure, got {:?}",
186 quot_value
187 ),
188 };
189
190 let reader = BufReader::new(file);
192 let mut current_stack = stack;
193
194 for line_result in reader.lines() {
195 match line_result {
196 Ok(mut line_str) => {
197 line_str.push('\n');
200
201 current_stack = unsafe { push(current_stack, Value::String(line_str.into())) };
203
204 if env_data.is_null() {
206 let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
208 unsafe { std::mem::transmute(wrapper) };
209 current_stack = unsafe { fn_ref(current_stack) };
210 } else {
211 let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
213 unsafe { std::mem::transmute(wrapper) };
214 current_stack = unsafe { fn_ref(current_stack, env_data, env_len) };
215 }
216
217 may::coroutine::yield_now();
219 }
220 Err(e) => {
221 let stack = unsafe { push(current_stack, Value::String(e.to_string().into())) };
223 return unsafe { push(stack, Value::Bool(false)) };
224 }
225 }
226 }
227
228 let stack = unsafe { push(current_stack, Value::String("".into())) };
230 unsafe { push(stack, Value::Bool(true)) }
231}
232
233#[unsafe(no_mangle)]
245pub unsafe extern "C" fn patch_seq_file_spit(stack: Stack) -> Stack {
246 assert!(!stack.is_null(), "file.spit: stack is empty");
247
248 let (stack, path_value) = unsafe { pop(stack) };
250 let path = match path_value {
251 Value::String(s) => s,
252 _ => panic!("file.spit: expected String path, got {:?}", path_value),
253 };
254
255 let (stack, content_value) = unsafe { pop(stack) };
257 let content = match content_value {
258 Value::String(s) => s,
259 _ => panic!(
260 "file.spit: expected String content, got {:?}",
261 content_value
262 ),
263 };
264
265 match fs::write(path_str(&path), content.as_bytes()) {
268 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
269 Err(_) => unsafe { push(stack, Value::Bool(false)) },
270 }
271}
272
273#[unsafe(no_mangle)]
285pub unsafe extern "C" fn patch_seq_file_append(stack: Stack) -> Stack {
286 assert!(!stack.is_null(), "file.append: stack is empty");
287
288 let (stack, path_value) = unsafe { pop(stack) };
290 let path = match path_value {
291 Value::String(s) => s,
292 _ => panic!("file.append: expected String path, got {:?}", path_value),
293 };
294
295 let (stack, content_value) = unsafe { pop(stack) };
297 let content = match content_value {
298 Value::String(s) => s,
299 _ => panic!(
300 "file.append: expected String content, got {:?}",
301 content_value
302 ),
303 };
304
305 let result = OpenOptions::new()
306 .create(true)
307 .append(true)
308 .open(path_str(&path))
309 .and_then(|mut file| file.write_all(content.as_bytes()));
310
311 match result {
312 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
313 Err(_) => unsafe { push(stack, Value::Bool(false)) },
314 }
315}
316
317#[unsafe(no_mangle)]
328pub unsafe extern "C" fn patch_seq_file_delete(stack: Stack) -> Stack {
329 assert!(!stack.is_null(), "file.delete: stack is empty");
330
331 let (stack, path_value) = unsafe { pop(stack) };
332 let path = match path_value {
333 Value::String(s) => s,
334 _ => panic!("file.delete: expected String path, got {:?}", path_value),
335 };
336
337 match fs::remove_file(path_str(&path)) {
338 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
339 Err(_) => unsafe { push(stack, Value::Bool(false)) },
340 }
341}
342
343#[unsafe(no_mangle)]
354pub unsafe extern "C" fn patch_seq_file_size(stack: Stack) -> Stack {
355 assert!(!stack.is_null(), "file.size: stack is empty");
356
357 let (stack, path_value) = unsafe { pop(stack) };
358 let path = match path_value {
359 Value::String(s) => s,
360 _ => panic!("file.size: expected String path, got {:?}", path_value),
361 };
362
363 match fs::metadata(path_str(&path)) {
364 Ok(metadata) => {
365 let size = metadata.len() as i64;
366 let stack = unsafe { push(stack, Value::Int(size)) };
367 unsafe { push(stack, Value::Bool(true)) }
368 }
369 Err(_) => {
370 let stack = unsafe { push(stack, Value::Int(0)) };
371 unsafe { push(stack, Value::Bool(false)) }
372 }
373 }
374}
375
376#[unsafe(no_mangle)]
390pub unsafe extern "C" fn patch_seq_dir_exists(stack: Stack) -> Stack {
391 assert!(!stack.is_null(), "dir.exists?: stack is empty");
392
393 let (stack, path_value) = unsafe { pop(stack) };
394 let path = match path_value {
395 Value::String(s) => s,
396 _ => panic!("dir.exists?: expected String path, got {:?}", path_value),
397 };
398
399 let exists = Path::new(path_str(&path)).is_dir();
400 unsafe { push(stack, Value::Bool(exists)) }
401}
402
403#[unsafe(no_mangle)]
414pub unsafe extern "C" fn patch_seq_dir_make(stack: Stack) -> Stack {
415 assert!(!stack.is_null(), "dir.make: stack is empty");
416
417 let (stack, path_value) = unsafe { pop(stack) };
418 let path = match path_value {
419 Value::String(s) => s,
420 _ => panic!("dir.make: expected String path, got {:?}", path_value),
421 };
422
423 match fs::create_dir_all(path_str(&path)) {
424 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
425 Err(_) => unsafe { push(stack, Value::Bool(false)) },
426 }
427}
428
429#[unsafe(no_mangle)]
440pub unsafe extern "C" fn patch_seq_dir_delete(stack: Stack) -> Stack {
441 assert!(!stack.is_null(), "dir.delete: stack is empty");
442
443 let (stack, path_value) = unsafe { pop(stack) };
444 let path = match path_value {
445 Value::String(s) => s,
446 _ => panic!("dir.delete: expected String path, got {:?}", path_value),
447 };
448
449 match fs::remove_dir(path_str(&path)) {
450 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
451 Err(_) => unsafe { push(stack, Value::Bool(false)) },
452 }
453}
454
455#[unsafe(no_mangle)]
466pub unsafe extern "C" fn patch_seq_dir_list(stack: Stack) -> Stack {
467 assert!(!stack.is_null(), "dir.list: stack is empty");
468
469 let (stack, path_value) = unsafe { pop(stack) };
470 let path = match path_value {
471 Value::String(s) => s,
472 _ => panic!("dir.list: expected String path, got {:?}", path_value),
473 };
474
475 match fs::read_dir(path_str(&path)) {
476 Ok(entries) => {
477 let mut names: Vec<Value> = Vec::new();
478 for entry in entries.flatten() {
479 if let Some(name) = entry.file_name().to_str() {
480 names.push(Value::String(name.to_string().into()));
481 }
482 }
483 let list = Value::Variant(Arc::new(VariantData::new(
484 crate::seqstring::global_string("List".to_string()),
485 names,
486 )));
487 let stack = unsafe { push(stack, list) };
488 unsafe { push(stack, Value::Bool(true)) }
489 }
490 Err(_) => {
491 let empty_list = Value::Variant(Arc::new(VariantData::new(
492 crate::seqstring::global_string("List".to_string()),
493 vec![],
494 )));
495 let stack = unsafe { push(stack, empty_list) };
496 unsafe { push(stack, Value::Bool(false)) }
497 }
498 }
499}
500
501pub use patch_seq_dir_delete as dir_delete;
503pub use patch_seq_dir_exists as dir_exists;
504pub use patch_seq_dir_list as dir_list;
505pub use patch_seq_dir_make as dir_make;
506pub use patch_seq_file_append as file_append;
507pub use patch_seq_file_delete as file_delete;
508pub use patch_seq_file_exists as file_exists;
509pub use patch_seq_file_for_each_line_plus as file_for_each_line_plus;
510pub use patch_seq_file_size as file_size;
511pub use patch_seq_file_slurp as file_slurp;
512pub use patch_seq_file_spit as file_spit;
513
514#[cfg(test)]
515mod tests;