1use crate::stack::{Stack, pop, push};
26use crate::value::Value;
27use std::fs;
28use std::path::Path;
29
30#[unsafe(no_mangle)]
41pub unsafe extern "C" fn patch_seq_file_slurp(stack: Stack) -> Stack {
42 assert!(!stack.is_null(), "file-slurp: stack is empty");
43
44 let (rest, value) = unsafe { pop(stack) };
45
46 match value {
47 Value::String(path) => {
48 let contents = fs::read_to_string(path.as_str()).unwrap_or_else(|e| {
49 panic!("file-slurp: failed to read '{}': {}", path.as_str(), e)
50 });
51
52 unsafe { push(rest, Value::String(contents.into())) }
53 }
54 _ => panic!("file-slurp: expected String path on stack, got {:?}", value),
55 }
56}
57
58#[unsafe(no_mangle)]
68pub unsafe extern "C" fn patch_seq_file_exists(stack: Stack) -> Stack {
69 assert!(!stack.is_null(), "file-exists?: stack is empty");
70
71 let (rest, value) = unsafe { pop(stack) };
72
73 match value {
74 Value::String(path) => {
75 let exists = if Path::new(path.as_str()).exists() {
76 1i64
77 } else {
78 0i64
79 };
80
81 unsafe { push(rest, Value::Int(exists)) }
82 }
83 _ => panic!(
84 "file-exists?: expected String path on stack, got {:?}",
85 value
86 ),
87 }
88}
89
90#[unsafe(no_mangle)]
102pub unsafe extern "C" fn patch_seq_file_slurp_safe(stack: Stack) -> Stack {
103 assert!(!stack.is_null(), "file-slurp-safe: stack is empty");
104
105 let (rest, value) = unsafe { pop(stack) };
106
107 match value {
108 Value::String(path) => match fs::read_to_string(path.as_str()) {
109 Ok(contents) => {
110 let stack = unsafe { push(rest, Value::String(contents.into())) };
111 unsafe { push(stack, Value::Int(1)) }
112 }
113 Err(_) => {
114 let stack = unsafe { push(rest, Value::String("".into())) };
115 unsafe { push(stack, Value::Int(0)) }
116 }
117 },
118 _ => panic!(
119 "file-slurp-safe: expected String path on stack, got {:?}",
120 value
121 ),
122 }
123}
124
125pub use patch_seq_file_exists as file_exists;
127pub use patch_seq_file_slurp as file_slurp;
128pub use patch_seq_file_slurp_safe as file_slurp_safe;
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use std::io::Write;
134 use tempfile::NamedTempFile;
135
136 #[test]
137 fn test_file_slurp() {
138 let mut temp_file = NamedTempFile::new().unwrap();
140 writeln!(temp_file, "Hello, file!").unwrap();
141 let path = temp_file.path().to_str().unwrap().to_string();
142
143 unsafe {
144 let stack = std::ptr::null_mut();
145 let stack = push(stack, Value::String(path.into()));
146 let stack = patch_seq_file_slurp(stack);
147
148 let (stack, value) = pop(stack);
149 match value {
150 Value::String(s) => assert_eq!(s.as_str().trim(), "Hello, file!"),
151 _ => panic!("Expected String"),
152 }
153 assert!(stack.is_null());
154 }
155 }
156
157 #[test]
158 fn test_file_exists_true() {
159 let temp_file = NamedTempFile::new().unwrap();
160 let path = temp_file.path().to_str().unwrap().to_string();
161
162 unsafe {
163 let stack = std::ptr::null_mut();
164 let stack = push(stack, Value::String(path.into()));
165 let stack = patch_seq_file_exists(stack);
166
167 let (stack, value) = pop(stack);
168 assert_eq!(value, Value::Int(1));
169 assert!(stack.is_null());
170 }
171 }
172
173 #[test]
174 fn test_file_exists_false() {
175 unsafe {
176 let stack = std::ptr::null_mut();
177 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
178 let stack = patch_seq_file_exists(stack);
179
180 let (stack, value) = pop(stack);
181 assert_eq!(value, Value::Int(0));
182 assert!(stack.is_null());
183 }
184 }
185
186 #[test]
187 fn test_file_slurp_utf8() {
188 let mut temp_file = NamedTempFile::new().unwrap();
189 write!(temp_file, "Hello, δΈη! π").unwrap();
190 let path = temp_file.path().to_str().unwrap().to_string();
191
192 unsafe {
193 let stack = std::ptr::null_mut();
194 let stack = push(stack, Value::String(path.into()));
195 let stack = patch_seq_file_slurp(stack);
196
197 let (stack, value) = pop(stack);
198 match value {
199 Value::String(s) => assert_eq!(s.as_str(), "Hello, δΈη! π"),
200 _ => panic!("Expected String"),
201 }
202 assert!(stack.is_null());
203 }
204 }
205
206 #[test]
207 fn test_file_slurp_empty() {
208 let temp_file = NamedTempFile::new().unwrap();
209 let path = temp_file.path().to_str().unwrap().to_string();
210
211 unsafe {
212 let stack = std::ptr::null_mut();
213 let stack = push(stack, Value::String(path.into()));
214 let stack = patch_seq_file_slurp(stack);
215
216 let (stack, value) = pop(stack);
217 match value {
218 Value::String(s) => assert_eq!(s.as_str(), ""),
219 _ => panic!("Expected String"),
220 }
221 assert!(stack.is_null());
222 }
223 }
224
225 #[test]
226 fn test_file_slurp_safe_success() {
227 let mut temp_file = NamedTempFile::new().unwrap();
228 writeln!(temp_file, "Safe read!").unwrap();
229 let path = temp_file.path().to_str().unwrap().to_string();
230
231 unsafe {
232 let stack = std::ptr::null_mut();
233 let stack = push(stack, Value::String(path.into()));
234 let stack = patch_seq_file_slurp_safe(stack);
235
236 let (stack, success) = pop(stack);
237 let (stack, contents) = pop(stack);
238 assert_eq!(success, Value::Int(1));
239 match contents {
240 Value::String(s) => assert_eq!(s.as_str().trim(), "Safe read!"),
241 _ => panic!("Expected String"),
242 }
243 assert!(stack.is_null());
244 }
245 }
246
247 #[test]
248 fn test_file_slurp_safe_not_found() {
249 unsafe {
250 let stack = std::ptr::null_mut();
251 let stack = push(stack, Value::String("/nonexistent/path/to/file.txt".into()));
252 let stack = patch_seq_file_slurp_safe(stack);
253
254 let (stack, success) = pop(stack);
255 let (stack, contents) = pop(stack);
256 assert_eq!(success, Value::Int(0));
257 match contents {
258 Value::String(s) => assert_eq!(s.as_str(), ""),
259 _ => panic!("Expected String"),
260 }
261 assert!(stack.is_null());
262 }
263 }
264
265 #[test]
266 fn test_file_slurp_safe_empty_file() {
267 let temp_file = NamedTempFile::new().unwrap();
268 let path = temp_file.path().to_str().unwrap().to_string();
269
270 unsafe {
271 let stack = std::ptr::null_mut();
272 let stack = push(stack, Value::String(path.into()));
273 let stack = patch_seq_file_slurp_safe(stack);
274
275 let (stack, success) = pop(stack);
276 let (stack, contents) = pop(stack);
277 assert_eq!(success, Value::Int(1)); match contents {
279 Value::String(s) => assert_eq!(s.as_str(), ""),
280 _ => panic!("Expected String"),
281 }
282 assert!(stack.is_null());
283 }
284 }
285}