Skip to main content

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::Bool(true)) // success
36            }
37            Err(_) => {
38                let stack = push(stack, Value::String(global_string(String::new())));
39                push(stack, Value::Bool(false)) // 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::Bool(true));
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::Bool(true));
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::Bool(false))
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::Bool(true)) // success
91            }
92            Err(_) => {
93                let stack = push(stack, Value::String(global_string(String::new())));
94                push(stack, Value::Bool(false)) // 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::Bool(exists))
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::Bool(is_file))
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::Bool(is_dir))
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 true on success, "" and false 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::Bool(true)) // success
239            }
240            None => {
241                let stack = push(stack, Value::String(global_string(String::new())));
242                push(stack, Value::Bool(false)) // 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 true on success, "" and false 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::Bool(true)) // success
273            }
274            None => {
275                let stack = push(stack, Value::String(global_string(String::new())));
276                push(stack, Value::Bool(false)) // no filename
277            }
278        }
279    }
280}
281
282/// Valid exit code range for Unix compatibility (only low 8 bits are meaningful)
283const EXIT_CODE_MIN: i64 = 0;
284const EXIT_CODE_MAX: i64 = 255;
285
286/// Exit the process with the given exit code
287///
288/// Stack effect: ( code -- )
289///
290/// Exit code must be in range 0-255 for Unix compatibility.
291/// This function does not return.
292///
293/// # Safety
294/// Stack must have an Int (exit code) on top.
295///
296/// Note: Returns `Stack` for LLVM ABI compatibility even though it never returns.
297#[unsafe(no_mangle)]
298pub unsafe extern "C" fn patch_seq_exit(stack: Stack) -> Stack {
299    unsafe {
300        let (_stack, code_val) = pop(stack);
301        let code = match code_val {
302            Value::Int(n) => {
303                if !(EXIT_CODE_MIN..=EXIT_CODE_MAX).contains(&n) {
304                    panic!(
305                        "os.exit: exit code must be in range {}-{}, got {}",
306                        EXIT_CODE_MIN, EXIT_CODE_MAX, n
307                    );
308                }
309                n as i32
310            }
311            _ => panic!(
312                "os.exit: expected Int (exit code) on stack, got {:?}",
313                code_val
314            ),
315        };
316
317        std::process::exit(code);
318    }
319}
320
321/// Get the operating system name
322///
323/// Stack effect: ( -- name )
324///
325/// Returns one of: "darwin", "linux", "windows", "freebsd", "openbsd", "netbsd",
326/// or "unknown" for unrecognized platforms.
327///
328/// # Safety
329/// Stack pointer must be valid
330#[unsafe(no_mangle)]
331pub unsafe extern "C" fn patch_seq_os_name(stack: Stack) -> Stack {
332    let name = if cfg!(target_os = "macos") {
333        "darwin"
334    } else if cfg!(target_os = "linux") {
335        "linux"
336    } else if cfg!(target_os = "windows") {
337        "windows"
338    } else if cfg!(target_os = "freebsd") {
339        "freebsd"
340    } else if cfg!(target_os = "openbsd") {
341        "openbsd"
342    } else if cfg!(target_os = "netbsd") {
343        "netbsd"
344    } else {
345        "unknown"
346    };
347
348    unsafe { push(stack, Value::String(global_string(name.to_owned()))) }
349}
350
351/// Get the CPU architecture
352///
353/// Stack effect: ( -- arch )
354///
355/// Returns one of: "x86_64", "aarch64", "arm", "x86", "riscv64",
356/// or "unknown" for unrecognized architectures.
357///
358/// # Safety
359/// Stack pointer must be valid
360#[unsafe(no_mangle)]
361pub unsafe extern "C" fn patch_seq_os_arch(stack: Stack) -> Stack {
362    let arch = if cfg!(target_arch = "x86_64") {
363        "x86_64"
364    } else if cfg!(target_arch = "aarch64") {
365        "aarch64"
366    } else if cfg!(target_arch = "arm") {
367        "arm"
368    } else if cfg!(target_arch = "x86") {
369        "x86"
370    } else if cfg!(target_arch = "riscv64") {
371        "riscv64"
372    } else {
373        "unknown"
374    };
375
376    unsafe { push(stack, Value::String(global_string(arch.to_owned()))) }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382    use crate::stack::{alloc_test_stack, pop, push};
383    use std::io::Write;
384    use tempfile::{NamedTempFile, TempDir};
385
386    // Helper to create a String value
387    fn str_val(s: &str) -> Value {
388        Value::String(global_string(s.to_string()))
389    }
390
391    // Helper to extract String from Value
392    fn as_str(v: &Value) -> &str {
393        match v {
394            Value::String(s) => s.as_str(),
395            _ => panic!("expected String, got {:?}", v),
396        }
397    }
398
399    // Helper to extract Bool from Value
400    fn as_bool(v: &Value) -> bool {
401        match v {
402            Value::Bool(b) => *b,
403            _ => panic!("expected Bool, got {:?}", v),
404        }
405    }
406
407    // ========================================================================
408    // Environment Variable Tests
409    // ========================================================================
410
411    #[test]
412    fn test_getenv_existing() {
413        // PATH should exist on all platforms
414        unsafe {
415            let stack = alloc_test_stack();
416            let stack = push(stack, str_val("PATH"));
417            let stack = patch_seq_getenv(stack);
418
419            let (stack, success) = pop(stack);
420            let (_, value) = pop(stack);
421
422            assert!(as_bool(&success), "PATH should exist");
423            assert!(!as_str(&value).is_empty(), "PATH should not be empty");
424        }
425    }
426
427    #[test]
428    fn test_getenv_nonexistent() {
429        unsafe {
430            let stack = alloc_test_stack();
431            let stack = push(stack, str_val("THIS_ENV_VAR_SHOULD_NOT_EXIST_12345"));
432            let stack = patch_seq_getenv(stack);
433
434            let (stack, success) = pop(stack);
435            let (_, value) = pop(stack);
436
437            assert!(!as_bool(&success), "nonexistent var should fail");
438            assert!(as_str(&value).is_empty(), "value should be empty string");
439        }
440    }
441
442    // ========================================================================
443    // Home Directory Tests
444    // ========================================================================
445
446    #[test]
447    fn test_home_dir() {
448        // HOME is typically set on Unix, and we set it in most CI environments
449        unsafe {
450            let stack = alloc_test_stack();
451            let stack = patch_seq_home_dir(stack);
452
453            let (stack, success) = pop(stack);
454            let (_, path) = pop(stack);
455
456            // On most systems HOME exists
457            if as_bool(&success) {
458                assert!(!as_str(&path).is_empty(), "home path should not be empty");
459            }
460            // If it doesn't exist, that's also valid (just returns false)
461        }
462    }
463
464    // ========================================================================
465    // Current Directory Tests
466    // ========================================================================
467
468    #[test]
469    fn test_current_dir() {
470        unsafe {
471            let stack = alloc_test_stack();
472            let stack = patch_seq_current_dir(stack);
473
474            let (stack, success) = pop(stack);
475            let (_, path) = pop(stack);
476
477            assert!(as_bool(&success), "current_dir should succeed");
478            assert!(!as_str(&path).is_empty(), "current dir should not be empty");
479
480            // Verify it matches std::env::current_dir
481            let expected = std::env::current_dir()
482                .unwrap()
483                .to_string_lossy()
484                .into_owned();
485            assert_eq!(as_str(&path), expected);
486        }
487    }
488
489    // ========================================================================
490    // Path Exists Tests
491    // ========================================================================
492
493    #[test]
494    fn test_path_exists_file() {
495        let tmp = NamedTempFile::new().unwrap();
496        let path = tmp.path().to_string_lossy().into_owned();
497
498        unsafe {
499            let stack = alloc_test_stack();
500            let stack = push(stack, str_val(&path));
501            let stack = patch_seq_path_exists(stack);
502
503            let (_, exists) = pop(stack);
504            assert!(as_bool(&exists), "temp file should exist");
505        }
506    }
507
508    #[test]
509    fn test_path_exists_dir() {
510        let tmp_dir = TempDir::new().unwrap();
511        let path = tmp_dir.path().to_string_lossy().into_owned();
512
513        unsafe {
514            let stack = alloc_test_stack();
515            let stack = push(stack, str_val(&path));
516            let stack = patch_seq_path_exists(stack);
517
518            let (_, exists) = pop(stack);
519            assert!(as_bool(&exists), "temp dir should exist");
520        }
521    }
522
523    #[test]
524    fn test_path_exists_nonexistent() {
525        unsafe {
526            let stack = alloc_test_stack();
527            let stack = push(stack, str_val("/this/path/should/not/exist/12345"));
528            let stack = patch_seq_path_exists(stack);
529
530            let (_, exists) = pop(stack);
531            assert!(!as_bool(&exists), "nonexistent path should not exist");
532        }
533    }
534
535    // ========================================================================
536    // Path Is File Tests
537    // ========================================================================
538
539    #[test]
540    fn test_path_is_file_true() {
541        let tmp = NamedTempFile::new().unwrap();
542        let path = tmp.path().to_string_lossy().into_owned();
543
544        unsafe {
545            let stack = alloc_test_stack();
546            let stack = push(stack, str_val(&path));
547            let stack = patch_seq_path_is_file(stack);
548
549            let (_, is_file) = pop(stack);
550            assert!(as_bool(&is_file), "temp file should be a file");
551        }
552    }
553
554    #[test]
555    fn test_path_is_file_false_for_dir() {
556        let tmp_dir = TempDir::new().unwrap();
557        let path = tmp_dir.path().to_string_lossy().into_owned();
558
559        unsafe {
560            let stack = alloc_test_stack();
561            let stack = push(stack, str_val(&path));
562            let stack = patch_seq_path_is_file(stack);
563
564            let (_, is_file) = pop(stack);
565            assert!(!as_bool(&is_file), "directory should not be a file");
566        }
567    }
568
569    #[test]
570    fn test_path_is_file_nonexistent() {
571        unsafe {
572            let stack = alloc_test_stack();
573            let stack = push(stack, str_val("/this/path/should/not/exist/12345"));
574            let stack = patch_seq_path_is_file(stack);
575
576            let (_, is_file) = pop(stack);
577            assert!(!as_bool(&is_file), "nonexistent path should not be a file");
578        }
579    }
580
581    // ========================================================================
582    // Path Is Dir Tests
583    // ========================================================================
584
585    #[test]
586    fn test_path_is_dir_true() {
587        let tmp_dir = TempDir::new().unwrap();
588        let path = tmp_dir.path().to_string_lossy().into_owned();
589
590        unsafe {
591            let stack = alloc_test_stack();
592            let stack = push(stack, str_val(&path));
593            let stack = patch_seq_path_is_dir(stack);
594
595            let (_, is_dir) = pop(stack);
596            assert!(as_bool(&is_dir), "temp dir should be a directory");
597        }
598    }
599
600    #[test]
601    fn test_path_is_dir_false_for_file() {
602        let tmp = NamedTempFile::new().unwrap();
603        let path = tmp.path().to_string_lossy().into_owned();
604
605        unsafe {
606            let stack = alloc_test_stack();
607            let stack = push(stack, str_val(&path));
608            let stack = patch_seq_path_is_dir(stack);
609
610            let (_, is_dir) = pop(stack);
611            assert!(!as_bool(&is_dir), "file should not be a directory");
612        }
613    }
614
615    #[test]
616    fn test_path_is_dir_nonexistent() {
617        unsafe {
618            let stack = alloc_test_stack();
619            let stack = push(stack, str_val("/this/path/should/not/exist/12345"));
620            let stack = patch_seq_path_is_dir(stack);
621
622            let (_, is_dir) = pop(stack);
623            assert!(
624                !as_bool(&is_dir),
625                "nonexistent path should not be a directory"
626            );
627        }
628    }
629
630    // ========================================================================
631    // Path Join Tests
632    // ========================================================================
633
634    #[test]
635    fn test_path_join_simple() {
636        unsafe {
637            let stack = alloc_test_stack();
638            let stack = push(stack, str_val("/home/user"));
639            let stack = push(stack, str_val("documents"));
640            let stack = patch_seq_path_join(stack);
641
642            let (_, joined) = pop(stack);
643            assert_eq!(as_str(&joined), "/home/user/documents");
644        }
645    }
646
647    #[test]
648    fn test_path_join_with_trailing_slash() {
649        unsafe {
650            let stack = alloc_test_stack();
651            let stack = push(stack, str_val("/home/user/"));
652            let stack = push(stack, str_val("documents"));
653            let stack = patch_seq_path_join(stack);
654
655            let (_, joined) = pop(stack);
656            assert_eq!(as_str(&joined), "/home/user/documents");
657        }
658    }
659
660    #[test]
661    fn test_path_join_absolute_component() {
662        // When joining with an absolute path, the absolute path replaces
663        unsafe {
664            let stack = alloc_test_stack();
665            let stack = push(stack, str_val("/home/user"));
666            let stack = push(stack, str_val("/etc/passwd"));
667            let stack = patch_seq_path_join(stack);
668
669            let (_, joined) = pop(stack);
670            // Rust's Path::join replaces when component is absolute
671            assert_eq!(as_str(&joined), "/etc/passwd");
672        }
673    }
674
675    #[test]
676    fn test_path_join_empty_component() {
677        unsafe {
678            let stack = alloc_test_stack();
679            let stack = push(stack, str_val("/home/user"));
680            let stack = push(stack, str_val(""));
681            let stack = patch_seq_path_join(stack);
682
683            let (_, joined) = pop(stack);
684            // Joining with empty string adds trailing slash
685            assert_eq!(as_str(&joined), "/home/user/");
686        }
687    }
688
689    // ========================================================================
690    // Path Parent Tests
691    // ========================================================================
692
693    #[test]
694    fn test_path_parent_normal() {
695        unsafe {
696            let stack = alloc_test_stack();
697            let stack = push(stack, str_val("/home/user/documents"));
698            let stack = patch_seq_path_parent(stack);
699
700            let (stack, success) = pop(stack);
701            let (_, parent) = pop(stack);
702
703            assert!(as_bool(&success), "should have parent");
704            assert_eq!(as_str(&parent), "/home/user");
705        }
706    }
707
708    #[test]
709    fn test_path_parent_root() {
710        unsafe {
711            let stack = alloc_test_stack();
712            let stack = push(stack, str_val("/"));
713            let stack = patch_seq_path_parent(stack);
714
715            let (stack, success) = pop(stack);
716            let (_, _parent) = pop(stack);
717
718            // On Unix, root "/" has no parent (returns None from Path::parent)
719            assert!(!as_bool(&success), "root has no parent");
720        }
721    }
722
723    #[test]
724    fn test_path_parent_single_component() {
725        unsafe {
726            let stack = alloc_test_stack();
727            let stack = push(stack, str_val("filename"));
728            let stack = patch_seq_path_parent(stack);
729
730            let (stack, success) = pop(stack);
731            let (_, parent) = pop(stack);
732
733            // Single component has parent "" (empty)
734            assert!(as_bool(&success), "single component has empty parent");
735            assert_eq!(as_str(&parent), "");
736        }
737    }
738
739    #[test]
740    fn test_path_parent_empty() {
741        unsafe {
742            let stack = alloc_test_stack();
743            let stack = push(stack, str_val(""));
744            let stack = patch_seq_path_parent(stack);
745
746            let (stack, success) = pop(stack);
747            let (_, _parent) = pop(stack);
748
749            // Empty path has no parent
750            assert!(!as_bool(&success), "empty path has no parent");
751        }
752    }
753
754    // ========================================================================
755    // Path Filename Tests
756    // ========================================================================
757
758    #[test]
759    fn test_path_filename_normal() {
760        unsafe {
761            let stack = alloc_test_stack();
762            let stack = push(stack, str_val("/home/user/document.txt"));
763            let stack = patch_seq_path_filename(stack);
764
765            let (stack, success) = pop(stack);
766            let (_, filename) = pop(stack);
767
768            assert!(as_bool(&success), "should have filename");
769            assert_eq!(as_str(&filename), "document.txt");
770        }
771    }
772
773    #[test]
774    fn test_path_filename_no_extension() {
775        unsafe {
776            let stack = alloc_test_stack();
777            let stack = push(stack, str_val("/home/user/document"));
778            let stack = patch_seq_path_filename(stack);
779
780            let (stack, success) = pop(stack);
781            let (_, filename) = pop(stack);
782
783            assert!(as_bool(&success), "should have filename");
784            assert_eq!(as_str(&filename), "document");
785        }
786    }
787
788    #[test]
789    fn test_path_filename_root() {
790        unsafe {
791            let stack = alloc_test_stack();
792            let stack = push(stack, str_val("/"));
793            let stack = patch_seq_path_filename(stack);
794
795            let (stack, success) = pop(stack);
796            let (_, _filename) = pop(stack);
797
798            // Root has no filename
799            assert!(!as_bool(&success), "root has no filename");
800        }
801    }
802
803    #[test]
804    fn test_path_filename_empty() {
805        unsafe {
806            let stack = alloc_test_stack();
807            let stack = push(stack, str_val(""));
808            let stack = patch_seq_path_filename(stack);
809
810            let (stack, success) = pop(stack);
811            let (_, _filename) = pop(stack);
812
813            // Empty path has no filename
814            assert!(!as_bool(&success), "empty path has no filename");
815        }
816    }
817
818    #[test]
819    fn test_path_filename_only_filename() {
820        unsafe {
821            let stack = alloc_test_stack();
822            let stack = push(stack, str_val("document.txt"));
823            let stack = patch_seq_path_filename(stack);
824
825            let (stack, success) = pop(stack);
826            let (_, filename) = pop(stack);
827
828            assert!(as_bool(&success), "should have filename");
829            assert_eq!(as_str(&filename), "document.txt");
830        }
831    }
832
833    // ========================================================================
834    // OS Name Tests
835    // ========================================================================
836
837    #[test]
838    fn test_os_name() {
839        unsafe {
840            let stack = alloc_test_stack();
841            let stack = patch_seq_os_name(stack);
842
843            let (_, name) = pop(stack);
844            let name_str = as_str(&name);
845
846            // Should be one of the known values
847            let valid_names = [
848                "darwin", "linux", "windows", "freebsd", "openbsd", "netbsd", "unknown",
849            ];
850            assert!(
851                valid_names.contains(&name_str),
852                "OS name '{}' should be one of {:?}",
853                name_str,
854                valid_names
855            );
856
857            // On the current platform, verify it matches expectations
858            #[cfg(target_os = "macos")]
859            assert_eq!(name_str, "darwin");
860            #[cfg(target_os = "linux")]
861            assert_eq!(name_str, "linux");
862            #[cfg(target_os = "windows")]
863            assert_eq!(name_str, "windows");
864        }
865    }
866
867    // ========================================================================
868    // OS Arch Tests
869    // ========================================================================
870
871    #[test]
872    fn test_os_arch() {
873        unsafe {
874            let stack = alloc_test_stack();
875            let stack = patch_seq_os_arch(stack);
876
877            let (_, arch) = pop(stack);
878            let arch_str = as_str(&arch);
879
880            // Should be one of the known values
881            let valid_archs = ["x86_64", "aarch64", "arm", "x86", "riscv64", "unknown"];
882            assert!(
883                valid_archs.contains(&arch_str),
884                "arch '{}' should be one of {:?}",
885                arch_str,
886                valid_archs
887            );
888
889            // On the current platform, verify it matches expectations
890            #[cfg(target_arch = "x86_64")]
891            assert_eq!(arch_str, "x86_64");
892            #[cfg(target_arch = "aarch64")]
893            assert_eq!(arch_str, "aarch64");
894        }
895    }
896
897    // ========================================================================
898    // Integration Tests - Real Filesystem Operations
899    // ========================================================================
900
901    #[test]
902    fn test_path_operations_integration() {
903        // Create a temp directory with a file
904        let tmp_dir = TempDir::new().unwrap();
905        let dir_path = tmp_dir.path().to_string_lossy().into_owned();
906
907        // Create a file in the directory
908        let file_path = tmp_dir.path().join("test.txt");
909        let mut file = std::fs::File::create(&file_path).unwrap();
910        file.write_all(b"test content").unwrap();
911        drop(file);
912
913        let file_path_str = file_path.to_string_lossy().into_owned();
914
915        unsafe {
916            // Test: path_exists on dir
917            let stack = alloc_test_stack();
918            let stack = push(stack, str_val(&dir_path));
919            let stack = patch_seq_path_exists(stack);
920            let (_, exists) = pop(stack);
921            assert!(as_bool(&exists));
922
923            // Test: path_is_dir on dir
924            let stack = alloc_test_stack();
925            let stack = push(stack, str_val(&dir_path));
926            let stack = patch_seq_path_is_dir(stack);
927            let (_, is_dir) = pop(stack);
928            assert!(as_bool(&is_dir));
929
930            // Test: path_exists on file
931            let stack = alloc_test_stack();
932            let stack = push(stack, str_val(&file_path_str));
933            let stack = patch_seq_path_exists(stack);
934            let (_, exists) = pop(stack);
935            assert!(as_bool(&exists));
936
937            // Test: path_is_file on file
938            let stack = alloc_test_stack();
939            let stack = push(stack, str_val(&file_path_str));
940            let stack = patch_seq_path_is_file(stack);
941            let (_, is_file) = pop(stack);
942            assert!(as_bool(&is_file));
943
944            // Test: path_filename
945            let stack = alloc_test_stack();
946            let stack = push(stack, str_val(&file_path_str));
947            let stack = patch_seq_path_filename(stack);
948            let (stack, success) = pop(stack);
949            let (_, filename) = pop(stack);
950            assert!(as_bool(&success));
951            assert_eq!(as_str(&filename), "test.txt");
952
953            // Test: path_parent gets back to directory
954            let stack = alloc_test_stack();
955            let stack = push(stack, str_val(&file_path_str));
956            let stack = patch_seq_path_parent(stack);
957            let (stack, success) = pop(stack);
958            let (_, parent) = pop(stack);
959            assert!(as_bool(&success));
960            assert_eq!(as_str(&parent), dir_path);
961        }
962    }
963
964    // Note: patch_seq_exit is not tested because it terminates the process
965}