seq_runtime/
os.rs

1//! OS operations for Seq
2//!
3//! Provides portable OS interaction primitives: environment variables,
4//! paths, and system information.
5//!
6//! These functions are exported with C ABI for LLVM codegen to call.
7
8use crate::seqstring::global_string;
9use crate::stack::{Stack, pop, push};
10use crate::value::Value;
11
12/// Get an environment variable
13///
14/// Stack effect: ( name -- value success )
15///
16/// Returns the value and 1 on success, "" and 0 on failure.
17///
18/// # Safety
19/// Stack must have a String (variable name) on top
20#[unsafe(no_mangle)]
21pub unsafe extern "C" fn patch_seq_getenv(stack: Stack) -> Stack {
22    unsafe {
23        let (stack, name_val) = pop(stack);
24        let name = match name_val {
25            Value::String(s) => s,
26            _ => panic!(
27                "getenv: expected String (name) on stack, got {:?}",
28                name_val
29            ),
30        };
31
32        match std::env::var(name.as_str()) {
33            Ok(value) => {
34                let stack = push(stack, Value::String(global_string(value)));
35                push(stack, Value::Int(1)) // success
36            }
37            Err(_) => {
38                let stack = push(stack, Value::String(global_string(String::new())));
39                push(stack, Value::Int(0)) // failure
40            }
41        }
42    }
43}
44
45/// Get the user's home directory
46///
47/// Stack effect: ( -- path success )
48///
49/// Returns the path and 1 on success, "" and 0 on failure.
50///
51/// # Safety
52/// Stack pointer must be valid
53#[unsafe(no_mangle)]
54pub unsafe extern "C" fn patch_seq_home_dir(stack: Stack) -> Stack {
55    unsafe {
56        // Try HOME env var first (works on Unix and some Windows configs)
57        if let Ok(home) = std::env::var("HOME") {
58            let stack = push(stack, Value::String(global_string(home)));
59            return push(stack, Value::Int(1));
60        }
61
62        // On Windows, try USERPROFILE
63        #[cfg(windows)]
64        if let Ok(home) = std::env::var("USERPROFILE") {
65            let stack = push(stack, Value::String(global_string(home)));
66            return push(stack, Value::Int(1));
67        }
68
69        // Fallback: return empty string with failure flag
70        let stack = push(stack, Value::String(global_string(String::new())));
71        push(stack, Value::Int(0))
72    }
73}
74
75/// Get the current working directory
76///
77/// Stack effect: ( -- path success )
78///
79/// Returns the path and 1 on success, "" and 0 on failure.
80///
81/// # Safety
82/// Stack pointer must be valid
83#[unsafe(no_mangle)]
84pub unsafe extern "C" fn patch_seq_current_dir(stack: Stack) -> Stack {
85    unsafe {
86        match std::env::current_dir() {
87            Ok(path) => {
88                let path_str = path.to_string_lossy().into_owned();
89                let stack = push(stack, Value::String(global_string(path_str)));
90                push(stack, Value::Int(1)) // success
91            }
92            Err(_) => {
93                let stack = push(stack, Value::String(global_string(String::new())));
94                push(stack, Value::Int(0)) // failure
95            }
96        }
97    }
98}
99
100/// Check if a path exists
101///
102/// Stack effect: ( path -- exists )
103///
104/// Returns 1 if path exists, 0 otherwise.
105///
106/// # Safety
107/// Stack must have a String (path) on top
108#[unsafe(no_mangle)]
109pub unsafe extern "C" fn patch_seq_path_exists(stack: Stack) -> Stack {
110    unsafe {
111        let (stack, path_val) = pop(stack);
112        let path = match path_val {
113            Value::String(s) => s,
114            _ => panic!(
115                "path-exists: expected String (path) on stack, got {:?}",
116                path_val
117            ),
118        };
119
120        let exists = std::path::Path::new(path.as_str()).exists();
121        push(stack, Value::Int(if exists { 1 } else { 0 }))
122    }
123}
124
125/// Check if a path is a regular file
126///
127/// Stack effect: ( path -- is-file )
128///
129/// Returns 1 if path is a regular file, 0 otherwise.
130///
131/// # Safety
132/// Stack must have a String (path) on top
133#[unsafe(no_mangle)]
134pub unsafe extern "C" fn patch_seq_path_is_file(stack: Stack) -> Stack {
135    unsafe {
136        let (stack, path_val) = pop(stack);
137        let path = match path_val {
138            Value::String(s) => s,
139            _ => panic!(
140                "path-is-file: expected String (path) on stack, got {:?}",
141                path_val
142            ),
143        };
144
145        let is_file = std::path::Path::new(path.as_str()).is_file();
146        push(stack, Value::Int(if is_file { 1 } else { 0 }))
147    }
148}
149
150/// Check if a path is a directory
151///
152/// Stack effect: ( path -- is-dir )
153///
154/// Returns 1 if path is a directory, 0 otherwise.
155///
156/// # Safety
157/// Stack must have a String (path) on top
158#[unsafe(no_mangle)]
159pub unsafe extern "C" fn patch_seq_path_is_dir(stack: Stack) -> Stack {
160    unsafe {
161        let (stack, path_val) = pop(stack);
162        let path = match path_val {
163            Value::String(s) => s,
164            _ => panic!(
165                "path-is-dir: expected String (path) on stack, got {:?}",
166                path_val
167            ),
168        };
169
170        let is_dir = std::path::Path::new(path.as_str()).is_dir();
171        push(stack, Value::Int(if is_dir { 1 } else { 0 }))
172    }
173}
174
175/// Join two path components
176///
177/// Stack effect: ( base component -- joined )
178///
179/// Joins the base path with the component using the platform's path separator.
180///
181/// # Safety
182/// Stack must have two Strings on top (base, then component)
183#[unsafe(no_mangle)]
184pub unsafe extern "C" fn patch_seq_path_join(stack: Stack) -> Stack {
185    unsafe {
186        let (stack, component_val) = pop(stack);
187        let (stack, base_val) = pop(stack);
188
189        let base = match base_val {
190            Value::String(s) => s,
191            _ => panic!(
192                "path-join: expected String (base) on stack, got {:?}",
193                base_val
194            ),
195        };
196
197        let component = match component_val {
198            Value::String(s) => s,
199            _ => panic!(
200                "path-join: expected String (component) on stack, got {:?}",
201                component_val
202            ),
203        };
204
205        let joined = std::path::Path::new(base.as_str())
206            .join(component.as_str())
207            .to_string_lossy()
208            .into_owned();
209
210        push(stack, Value::String(global_string(joined)))
211    }
212}
213
214/// Get the parent directory of a path
215///
216/// Stack effect: ( path -- parent success )
217///
218/// Returns the parent directory and 1 on success, "" and 0 if no parent.
219///
220/// # Safety
221/// Stack must have a String (path) on top
222#[unsafe(no_mangle)]
223pub unsafe extern "C" fn patch_seq_path_parent(stack: Stack) -> Stack {
224    unsafe {
225        let (stack, path_val) = pop(stack);
226        let path = match path_val {
227            Value::String(s) => s,
228            _ => panic!(
229                "path-parent: expected String (path) on stack, got {:?}",
230                path_val
231            ),
232        };
233
234        match std::path::Path::new(path.as_str()).parent() {
235            Some(parent) => {
236                let parent_str = parent.to_string_lossy().into_owned();
237                let stack = push(stack, Value::String(global_string(parent_str)));
238                push(stack, Value::Int(1)) // success
239            }
240            None => {
241                let stack = push(stack, Value::String(global_string(String::new())));
242                push(stack, Value::Int(0)) // no parent
243            }
244        }
245    }
246}
247
248/// Get the filename component of a path
249///
250/// Stack effect: ( path -- filename success )
251///
252/// Returns the filename and 1 on success, "" and 0 if no filename.
253///
254/// # Safety
255/// Stack must have a String (path) on top
256#[unsafe(no_mangle)]
257pub unsafe extern "C" fn patch_seq_path_filename(stack: Stack) -> Stack {
258    unsafe {
259        let (stack, path_val) = pop(stack);
260        let path = match path_val {
261            Value::String(s) => s,
262            _ => panic!(
263                "path-filename: expected String (path) on stack, got {:?}",
264                path_val
265            ),
266        };
267
268        match std::path::Path::new(path.as_str()).file_name() {
269            Some(filename) => {
270                let filename_str = filename.to_string_lossy().into_owned();
271                let stack = push(stack, Value::String(global_string(filename_str)));
272                push(stack, Value::Int(1)) // success
273            }
274            None => {
275                let stack = push(stack, Value::String(global_string(String::new())));
276                push(stack, Value::Int(0)) // no filename
277            }
278        }
279    }
280}