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)]
45pub unsafe extern "C" fn patch_seq_file_slurp(stack: Stack) -> Stack {
46 assert!(!stack.is_null(), "file-slurp: stack is empty");
47
48 let (rest, value) = unsafe { pop(stack) };
49
50 match value {
51 Value::String(path) => match fs::read_to_string(path.as_str()) {
52 Ok(contents) => {
53 let stack = unsafe { push(rest, Value::String(contents.into())) };
54 unsafe { push(stack, Value::Bool(true)) }
55 }
56 Err(_) => {
57 let stack = unsafe { push(rest, Value::String("".into())) };
58 unsafe { push(stack, Value::Bool(false)) }
59 }
60 },
61 _ => panic!("file-slurp: expected String path on stack, got {:?}", value),
62 }
63}
64
65#[unsafe(no_mangle)]
75pub unsafe extern "C" fn patch_seq_file_exists(stack: Stack) -> Stack {
76 assert!(!stack.is_null(), "file-exists?: stack is empty");
77
78 let (rest, value) = unsafe { pop(stack) };
79
80 match value {
81 Value::String(path) => {
82 let exists = Path::new(path.as_str()).exists();
83 unsafe { push(rest, Value::Bool(exists)) }
84 }
85 _ => panic!(
86 "file-exists?: expected String path on stack, got {:?}",
87 value
88 ),
89 }
90}
91
92#[unsafe(no_mangle)]
128pub unsafe extern "C" fn patch_seq_file_for_each_line_plus(stack: Stack) -> Stack {
129 assert!(!stack.is_null(), "file-for-each-line+: stack is empty");
130
131 let (stack, quot_value) = unsafe { pop(stack) };
133
134 let (stack, path_value) = unsafe { pop(stack) };
136 let path = match path_value {
137 Value::String(s) => s,
138 _ => panic!(
139 "file-for-each-line+: expected String path, got {:?}",
140 path_value
141 ),
142 };
143
144 let file = match File::open(path.as_str()) {
146 Ok(f) => f,
147 Err(e) => {
148 let stack = unsafe { push(stack, Value::String(e.to_string().into())) };
150 return unsafe { push(stack, Value::Int(0)) };
151 }
152 };
153
154 let (wrapper, env_data, env_len): (usize, *const Value, usize) = match quot_value {
156 Value::Quotation { wrapper, .. } => {
157 if wrapper == 0 {
158 panic!("file-for-each-line+: quotation wrapper function pointer is null");
159 }
160 (wrapper, std::ptr::null(), 0)
161 }
162 Value::Closure { fn_ptr, ref env } => {
163 if fn_ptr == 0 {
164 panic!("file-for-each-line+: closure function pointer is null");
165 }
166 (fn_ptr, env.as_ptr(), env.len())
167 }
168 _ => panic!(
169 "file-for-each-line+: expected Quotation or Closure, got {:?}",
170 quot_value
171 ),
172 };
173
174 let reader = BufReader::new(file);
176 let mut current_stack = stack;
177
178 for line_result in reader.lines() {
179 match line_result {
180 Ok(mut line_str) => {
181 line_str.push('\n');
184
185 current_stack = unsafe { push(current_stack, Value::String(line_str.into())) };
187
188 if env_data.is_null() {
190 let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
192 unsafe { std::mem::transmute(wrapper) };
193 current_stack = unsafe { fn_ref(current_stack) };
194 } else {
195 let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
197 unsafe { std::mem::transmute(wrapper) };
198 current_stack = unsafe { fn_ref(current_stack, env_data, env_len) };
199 }
200
201 may::coroutine::yield_now();
203 }
204 Err(e) => {
205 let stack = unsafe { push(current_stack, Value::String(e.to_string().into())) };
207 return unsafe { push(stack, Value::Bool(false)) };
208 }
209 }
210 }
211
212 let stack = unsafe { push(current_stack, Value::String("".into())) };
214 unsafe { push(stack, Value::Bool(true)) }
215}
216
217pub use patch_seq_file_exists as file_exists;
219pub use patch_seq_file_for_each_line_plus as file_for_each_line_plus;
220pub use patch_seq_file_slurp as file_slurp;
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use std::io::Write;
226 use tempfile::NamedTempFile;
227
228 #[test]
229 fn test_file_slurp() {
230 let mut temp_file = NamedTempFile::new().unwrap();
232 writeln!(temp_file, "Hello, file!").unwrap();
233 let path = temp_file.path().to_str().unwrap().to_string();
234
235 unsafe {
236 let stack = crate::stack::alloc_test_stack();
237 let stack = push(stack, Value::String(path.into()));
238 let stack = patch_seq_file_slurp(stack);
239
240 let (stack, success) = pop(stack);
242 assert_eq!(success, Value::Bool(true));
243 let (_stack, value) = pop(stack);
244 match value {
245 Value::String(s) => assert_eq!(s.as_str().trim(), "Hello, file!"),
246 _ => panic!("Expected String"),
247 }
248 }
249 }
250
251 #[test]
252 fn test_file_exists_true() {
253 let temp_file = NamedTempFile::new().unwrap();
254 let path = temp_file.path().to_str().unwrap().to_string();
255
256 unsafe {
257 let stack = crate::stack::alloc_test_stack();
258 let stack = push(stack, Value::String(path.into()));
259 let stack = patch_seq_file_exists(stack);
260
261 let (_stack, value) = pop(stack);
262 assert_eq!(value, Value::Bool(true));
263 }
264 }
265
266 #[test]
267 fn test_file_exists_false() {
268 unsafe {
269 let stack = crate::stack::alloc_test_stack();
270 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
271 let stack = patch_seq_file_exists(stack);
272
273 let (_stack, value) = pop(stack);
274 assert_eq!(value, Value::Bool(false));
275 }
276 }
277
278 #[test]
279 fn test_file_slurp_utf8() {
280 let mut temp_file = NamedTempFile::new().unwrap();
281 write!(temp_file, "Hello, δΈη! π").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_slurp(stack);
288
289 let (stack, success) = pop(stack);
291 assert_eq!(success, Value::Bool(true));
292 let (_stack, value) = pop(stack);
293 match value {
294 Value::String(s) => assert_eq!(s.as_str(), "Hello, δΈη! π"),
295 _ => panic!("Expected String"),
296 }
297 }
298 }
299
300 #[test]
301 fn test_file_slurp_empty() {
302 let temp_file = NamedTempFile::new().unwrap();
303 let path = temp_file.path().to_str().unwrap().to_string();
304
305 unsafe {
306 let stack = crate::stack::alloc_test_stack();
307 let stack = push(stack, Value::String(path.into()));
308 let stack = patch_seq_file_slurp(stack);
309
310 let (stack, success) = pop(stack);
312 assert_eq!(success, Value::Bool(true)); let (_stack, value) = pop(stack);
314 match value {
315 Value::String(s) => assert_eq!(s.as_str(), ""),
316 _ => panic!("Expected String"),
317 }
318 }
319 }
320
321 #[test]
322 fn test_file_slurp_not_found() {
323 unsafe {
324 let stack = crate::stack::alloc_test_stack();
325 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
326 let stack = patch_seq_file_slurp(stack);
327
328 let (stack, success) = pop(stack);
329 let (_stack, contents) = pop(stack);
330 assert_eq!(success, Value::Bool(false));
331 match contents {
332 Value::String(s) => assert_eq!(s.as_str(), ""),
333 _ => panic!("Expected String"),
334 }
335 }
336 }
337}