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)]
141pub unsafe extern "C" fn patch_seq_file_for_each_line(stack: Stack) -> Stack {
142 assert!(!stack.is_null(), "file.for-each-line: stack is empty");
143
144 let (stack, quot_value) = unsafe { pop(stack) };
146
147 let (stack, path_value) = unsafe { pop(stack) };
149 let path = match path_value {
150 Value::String(s) => s,
151 _ => panic!(
152 "file.for-each-line: expected String path, got {:?}",
153 path_value
154 ),
155 };
156
157 let file = match File::open(path_str(&path)) {
159 Ok(f) => f,
160 Err(_) => return unsafe { push(stack, Value::Bool(false)) },
161 };
162
163 let (wrapper, env_data, env_len): (usize, *const Value, usize) = match quot_value {
165 Value::Quotation { wrapper, .. } => {
166 if wrapper == 0 {
167 panic!("file.for-each-line: quotation wrapper function pointer is null");
168 }
169 (wrapper, std::ptr::null(), 0)
170 }
171 Value::Closure { fn_ptr, ref env } => {
172 if fn_ptr == 0 {
173 panic!("file.for-each-line: closure function pointer is null");
174 }
175 (fn_ptr, env.as_ptr(), env.len())
176 }
177 _ => panic!(
178 "file.for-each-line: expected Quotation or Closure, got {:?}",
179 quot_value
180 ),
181 };
182
183 let reader = BufReader::new(file);
185 let mut current_stack = stack;
186
187 for line_result in reader.lines() {
188 match line_result {
189 Ok(mut line_str) => {
190 line_str.push('\n');
193
194 current_stack = unsafe { push(current_stack, Value::String(line_str.into())) };
196
197 if env_data.is_null() {
199 let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
201 unsafe { std::mem::transmute(wrapper) };
202 current_stack = unsafe { fn_ref(current_stack) };
203 } else {
204 let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
206 unsafe { std::mem::transmute(wrapper) };
207 current_stack = unsafe { fn_ref(current_stack, env_data, env_len) };
208 }
209
210 may::coroutine::yield_now();
212 }
213 Err(_) => {
214 return unsafe { push(current_stack, Value::Bool(false)) };
216 }
217 }
218 }
219
220 unsafe { push(current_stack, Value::Bool(true)) }
221}
222
223#[unsafe(no_mangle)]
235pub unsafe extern "C" fn patch_seq_file_spit(stack: Stack) -> Stack {
236 assert!(!stack.is_null(), "file.spit: stack is empty");
237
238 let (stack, path_value) = unsafe { pop(stack) };
240 let path = match path_value {
241 Value::String(s) => s,
242 _ => panic!("file.spit: expected String path, got {:?}", path_value),
243 };
244
245 let (stack, content_value) = unsafe { pop(stack) };
247 let content = match content_value {
248 Value::String(s) => s,
249 _ => panic!(
250 "file.spit: expected String content, got {:?}",
251 content_value
252 ),
253 };
254
255 match fs::write(path_str(&path), content.as_bytes()) {
258 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
259 Err(_) => unsafe { push(stack, Value::Bool(false)) },
260 }
261}
262
263#[unsafe(no_mangle)]
275pub unsafe extern "C" fn patch_seq_file_append(stack: Stack) -> Stack {
276 assert!(!stack.is_null(), "file.append: stack is empty");
277
278 let (stack, path_value) = unsafe { pop(stack) };
280 let path = match path_value {
281 Value::String(s) => s,
282 _ => panic!("file.append: expected String path, got {:?}", path_value),
283 };
284
285 let (stack, content_value) = unsafe { pop(stack) };
287 let content = match content_value {
288 Value::String(s) => s,
289 _ => panic!(
290 "file.append: expected String content, got {:?}",
291 content_value
292 ),
293 };
294
295 let result = OpenOptions::new()
296 .create(true)
297 .append(true)
298 .open(path_str(&path))
299 .and_then(|mut file| file.write_all(content.as_bytes()));
300
301 match result {
302 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
303 Err(_) => unsafe { push(stack, Value::Bool(false)) },
304 }
305}
306
307#[unsafe(no_mangle)]
318pub unsafe extern "C" fn patch_seq_file_delete(stack: Stack) -> Stack {
319 assert!(!stack.is_null(), "file.delete: stack is empty");
320
321 let (stack, path_value) = unsafe { pop(stack) };
322 let path = match path_value {
323 Value::String(s) => s,
324 _ => panic!("file.delete: expected String path, got {:?}", path_value),
325 };
326
327 match fs::remove_file(path_str(&path)) {
328 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
329 Err(_) => unsafe { push(stack, Value::Bool(false)) },
330 }
331}
332
333#[unsafe(no_mangle)]
344pub unsafe extern "C" fn patch_seq_file_size(stack: Stack) -> Stack {
345 assert!(!stack.is_null(), "file.size: stack is empty");
346
347 let (stack, path_value) = unsafe { pop(stack) };
348 let path = match path_value {
349 Value::String(s) => s,
350 _ => panic!("file.size: expected String path, got {:?}", path_value),
351 };
352
353 match fs::metadata(path_str(&path)) {
354 Ok(metadata) => {
355 let size = metadata.len() as i64;
356 let stack = unsafe { push(stack, Value::Int(size)) };
357 unsafe { push(stack, Value::Bool(true)) }
358 }
359 Err(_) => {
360 let stack = unsafe { push(stack, Value::Int(0)) };
361 unsafe { push(stack, Value::Bool(false)) }
362 }
363 }
364}
365
366#[unsafe(no_mangle)]
380pub unsafe extern "C" fn patch_seq_dir_exists(stack: Stack) -> Stack {
381 assert!(!stack.is_null(), "dir.exists?: stack is empty");
382
383 let (stack, path_value) = unsafe { pop(stack) };
384 let path = match path_value {
385 Value::String(s) => s,
386 _ => panic!("dir.exists?: expected String path, got {:?}", path_value),
387 };
388
389 let exists = Path::new(path_str(&path)).is_dir();
390 unsafe { push(stack, Value::Bool(exists)) }
391}
392
393#[unsafe(no_mangle)]
404pub unsafe extern "C" fn patch_seq_dir_make(stack: Stack) -> Stack {
405 assert!(!stack.is_null(), "dir.make: stack is empty");
406
407 let (stack, path_value) = unsafe { pop(stack) };
408 let path = match path_value {
409 Value::String(s) => s,
410 _ => panic!("dir.make: expected String path, got {:?}", path_value),
411 };
412
413 match fs::create_dir_all(path_str(&path)) {
414 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
415 Err(_) => unsafe { push(stack, Value::Bool(false)) },
416 }
417}
418
419#[unsafe(no_mangle)]
430pub unsafe extern "C" fn patch_seq_dir_delete(stack: Stack) -> Stack {
431 assert!(!stack.is_null(), "dir.delete: stack is empty");
432
433 let (stack, path_value) = unsafe { pop(stack) };
434 let path = match path_value {
435 Value::String(s) => s,
436 _ => panic!("dir.delete: expected String path, got {:?}", path_value),
437 };
438
439 match fs::remove_dir(path_str(&path)) {
440 Ok(()) => unsafe { push(stack, Value::Bool(true)) },
441 Err(_) => unsafe { push(stack, Value::Bool(false)) },
442 }
443}
444
445#[unsafe(no_mangle)]
456pub unsafe extern "C" fn patch_seq_dir_list(stack: Stack) -> Stack {
457 assert!(!stack.is_null(), "dir.list: stack is empty");
458
459 let (stack, path_value) = unsafe { pop(stack) };
460 let path = match path_value {
461 Value::String(s) => s,
462 _ => panic!("dir.list: expected String path, got {:?}", path_value),
463 };
464
465 match fs::read_dir(path_str(&path)) {
466 Ok(entries) => {
467 let mut names: Vec<Value> = Vec::new();
468 for entry in entries.flatten() {
469 if let Some(name) = entry.file_name().to_str() {
470 names.push(Value::String(name.to_string().into()));
471 }
472 }
473 let list = Value::Variant(Arc::new(VariantData::new(
474 crate::seqstring::global_string("List".to_string()),
475 names,
476 )));
477 let stack = unsafe { push(stack, list) };
478 unsafe { push(stack, Value::Bool(true)) }
479 }
480 Err(_) => {
481 let empty_list = Value::Variant(Arc::new(VariantData::new(
482 crate::seqstring::global_string("List".to_string()),
483 vec![],
484 )));
485 let stack = unsafe { push(stack, empty_list) };
486 unsafe { push(stack, Value::Bool(false)) }
487 }
488 }
489}
490
491pub use patch_seq_dir_delete as dir_delete;
493pub use patch_seq_dir_exists as dir_exists;
494pub use patch_seq_dir_list as dir_list;
495pub use patch_seq_dir_make as dir_make;
496pub use patch_seq_file_append as file_append;
497pub use patch_seq_file_delete as file_delete;
498pub use patch_seq_file_exists as file_exists;
499pub use patch_seq_file_for_each_line as file_for_each_line;
500pub use patch_seq_file_size as file_size;
501pub use patch_seq_file_slurp as file_slurp;
502pub use patch_seq_file_spit as file_spit;
503
504#[cfg(test)]
505mod tests;