1use crate::seqstring::global_string;
9use crate::stack::{Stack, pop, push};
10use crate::value::Value;
11
12#[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)) }
37 Err(_) => {
38 let stack = push(stack, Value::String(global_string(String::new())));
39 push(stack, Value::Bool(false)) }
41 }
42 }
43}
44
45#[unsafe(no_mangle)]
54pub unsafe extern "C" fn patch_seq_home_dir(stack: Stack) -> Stack {
55 unsafe {
56 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 #[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 let stack = push(stack, Value::String(global_string(String::new())));
71 push(stack, Value::Bool(false))
72 }
73}
74
75#[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)) }
92 Err(_) => {
93 let stack = push(stack, Value::String(global_string(String::new())));
94 push(stack, Value::Bool(false)) }
96 }
97 }
98}
99
100#[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#[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#[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#[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#[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)) }
240 None => {
241 let stack = push(stack, Value::String(global_string(String::new())));
242 push(stack, Value::Bool(false)) }
244 }
245 }
246}
247
248#[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)) }
274 None => {
275 let stack = push(stack, Value::String(global_string(String::new())));
276 push(stack, Value::Bool(false)) }
278 }
279 }
280}
281
282const EXIT_CODE_MIN: i64 = 0;
284const EXIT_CODE_MAX: i64 = 255;
285
286#[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#[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#[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 fn str_val(s: &str) -> Value {
388 Value::String(global_string(s.to_string()))
389 }
390
391 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 fn as_bool(v: &Value) -> bool {
401 match v {
402 Value::Bool(b) => *b,
403 _ => panic!("expected Bool, got {:?}", v),
404 }
405 }
406
407 #[test]
412 fn test_getenv_existing() {
413 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 #[test]
447 fn test_home_dir() {
448 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 if as_bool(&success) {
458 assert!(!as_str(&path).is_empty(), "home path should not be empty");
459 }
460 }
462 }
463
464 #[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 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 #[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 #[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 #[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 #[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 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 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 assert_eq!(as_str(&joined), "/home/user/");
686 }
687 }
688
689 #[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 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 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 assert!(!as_bool(&success), "empty path has no parent");
751 }
752 }
753
754 #[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 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 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 #[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 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 #[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 #[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 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 #[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 #[test]
902 fn test_path_operations_integration() {
903 let tmp_dir = TempDir::new().unwrap();
905 let dir_path = tmp_dir.path().to_string_lossy().into_owned();
906
907 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 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 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 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 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 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 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 }