1use crate::seqstring::global_string;
14use crate::stack::{Stack, pop, push};
15use crate::value::Value;
16
17#[unsafe(no_mangle)]
26pub unsafe extern "C" fn patch_seq_string_split(stack: Stack) -> Stack {
27 use crate::value::VariantData;
28
29 assert!(!stack.is_null(), "string_split: stack is empty");
30
31 let (stack, delim_val) = unsafe { pop(stack) };
32 assert!(!stack.is_null(), "string_split: need two strings");
33 let (stack, str_val) = unsafe { pop(stack) };
34
35 match (str_val, delim_val) {
36 (Value::String(s), Value::String(d)) => {
37 let fields: Vec<Value> = s
39 .as_str()
40 .split(d.as_str())
41 .map(|part| Value::String(global_string(part.to_owned())))
42 .collect();
43
44 let variant = Value::Variant(Box::new(VariantData::new(0, fields)));
46
47 unsafe { push(stack, variant) }
48 }
49 _ => panic!("string_split: expected two strings on stack"),
50 }
51}
52
53#[unsafe(no_mangle)]
60pub unsafe extern "C" fn patch_seq_string_empty(stack: Stack) -> Stack {
61 assert!(!stack.is_null(), "string_empty: stack is empty");
62
63 let (stack, value) = unsafe { pop(stack) };
64
65 match value {
66 Value::String(s) => {
67 let is_empty = s.as_str().is_empty();
68 let result = if is_empty { 1 } else { 0 };
69 unsafe { push(stack, Value::Int(result)) }
70 }
71 _ => panic!("string_empty: expected String on stack"),
72 }
73}
74
75#[unsafe(no_mangle)]
82pub unsafe extern "C" fn patch_seq_string_contains(stack: Stack) -> Stack {
83 assert!(!stack.is_null(), "string_contains: stack is empty");
84
85 let (stack, substring_val) = unsafe { pop(stack) };
86 assert!(!stack.is_null(), "string_contains: need two strings");
87 let (stack, str_val) = unsafe { pop(stack) };
88
89 match (str_val, substring_val) {
90 (Value::String(s), Value::String(sub)) => {
91 let contains = s.as_str().contains(sub.as_str());
92 let result = if contains { 1 } else { 0 };
93 unsafe { push(stack, Value::Int(result)) }
94 }
95 _ => panic!("string_contains: expected two strings on stack"),
96 }
97}
98
99#[unsafe(no_mangle)]
106pub unsafe extern "C" fn patch_seq_string_starts_with(stack: Stack) -> Stack {
107 assert!(!stack.is_null(), "string_starts_with: stack is empty");
108
109 let (stack, prefix_val) = unsafe { pop(stack) };
110 assert!(!stack.is_null(), "string_starts_with: need two strings");
111 let (stack, str_val) = unsafe { pop(stack) };
112
113 match (str_val, prefix_val) {
114 (Value::String(s), Value::String(prefix)) => {
115 let starts = s.as_str().starts_with(prefix.as_str());
116 let result = if starts { 1 } else { 0 };
117 unsafe { push(stack, Value::Int(result)) }
118 }
119 _ => panic!("string_starts_with: expected two strings on stack"),
120 }
121}
122
123#[unsafe(no_mangle)]
130pub unsafe extern "C" fn patch_seq_string_concat(stack: Stack) -> Stack {
131 assert!(!stack.is_null(), "string_concat: stack is empty");
132
133 let (stack, str2_val) = unsafe { pop(stack) };
134 assert!(!stack.is_null(), "string_concat: need two strings");
135 let (stack, str1_val) = unsafe { pop(stack) };
136
137 match (str1_val, str2_val) {
138 (Value::String(s1), Value::String(s2)) => {
139 let result = format!("{}{}", s1.as_str(), s2.as_str());
140 unsafe { push(stack, Value::String(global_string(result))) }
141 }
142 _ => panic!("string_concat: expected two strings on stack"),
143 }
144}
145
146#[unsafe(no_mangle)]
156pub unsafe extern "C" fn patch_seq_string_length(stack: Stack) -> Stack {
157 assert!(!stack.is_null(), "string_length: stack is empty");
158
159 let (stack, str_val) = unsafe { pop(stack) };
160
161 match str_val {
162 Value::String(s) => {
163 let len = s.as_str().chars().count() as i64;
164 unsafe { push(stack, Value::Int(len)) }
165 }
166 _ => panic!("string_length: expected String on stack"),
167 }
168}
169
170#[unsafe(no_mangle)]
179pub unsafe extern "C" fn patch_seq_string_byte_length(stack: Stack) -> Stack {
180 assert!(!stack.is_null(), "string_byte_length: stack is empty");
181
182 let (stack, str_val) = unsafe { pop(stack) };
183
184 match str_val {
185 Value::String(s) => {
186 let len = s.as_str().len() as i64;
187 unsafe { push(stack, Value::Int(len)) }
188 }
189 _ => panic!("string_byte_length: expected String on stack"),
190 }
191}
192
193#[unsafe(no_mangle)]
203pub unsafe extern "C" fn patch_seq_string_char_at(stack: Stack) -> Stack {
204 assert!(!stack.is_null(), "string_char_at: stack is empty");
205
206 let (stack, index_val) = unsafe { pop(stack) };
207 assert!(!stack.is_null(), "string_char_at: need string and index");
208 let (stack, str_val) = unsafe { pop(stack) };
209
210 match (str_val, index_val) {
211 (Value::String(s), Value::Int(index)) => {
212 let result = if index < 0 {
213 -1
214 } else {
215 s.as_str()
216 .chars()
217 .nth(index as usize)
218 .map(|c| c as i64)
219 .unwrap_or(-1)
220 };
221 unsafe { push(stack, Value::Int(result)) }
222 }
223 _ => panic!("string_char_at: expected String and Int on stack"),
224 }
225}
226
227#[unsafe(no_mangle)]
243pub unsafe extern "C" fn patch_seq_string_substring(stack: Stack) -> Stack {
244 assert!(!stack.is_null(), "string_substring: stack is empty");
245
246 let (stack, len_val) = unsafe { pop(stack) };
247 assert!(
248 !stack.is_null(),
249 "string_substring: need string, start, len"
250 );
251 let (stack, start_val) = unsafe { pop(stack) };
252 assert!(
253 !stack.is_null(),
254 "string_substring: need string, start, len"
255 );
256 let (stack, str_val) = unsafe { pop(stack) };
257
258 match (str_val, start_val, len_val) {
259 (Value::String(s), Value::Int(start), Value::Int(len)) => {
260 let result = if start < 0 || len < 0 {
261 String::new()
262 } else {
263 s.as_str()
264 .chars()
265 .skip(start as usize)
266 .take(len as usize)
267 .collect()
268 };
269 unsafe { push(stack, Value::String(global_string(result))) }
270 }
271 _ => panic!("string_substring: expected String, Int, Int on stack"),
272 }
273}
274
275#[unsafe(no_mangle)]
285pub unsafe extern "C" fn patch_seq_char_to_string(stack: Stack) -> Stack {
286 assert!(!stack.is_null(), "char_to_string: stack is empty");
287
288 let (stack, code_point_val) = unsafe { pop(stack) };
289
290 match code_point_val {
291 Value::Int(code_point) => {
292 let result = if !(0..=0x10FFFF).contains(&code_point) {
293 String::new()
295 } else {
296 match char::from_u32(code_point as u32) {
297 Some(c) => c.to_string(),
298 None => String::new(), }
300 };
301 unsafe { push(stack, Value::String(global_string(result))) }
302 }
303 _ => panic!("char_to_string: expected Int on stack"),
304 }
305}
306
307#[unsafe(no_mangle)]
317pub unsafe extern "C" fn patch_seq_string_find(stack: Stack) -> Stack {
318 assert!(!stack.is_null(), "string_find: stack is empty");
319
320 let (stack, needle_val) = unsafe { pop(stack) };
321 assert!(!stack.is_null(), "string_find: need string and needle");
322 let (stack, str_val) = unsafe { pop(stack) };
323
324 match (str_val, needle_val) {
325 (Value::String(haystack), Value::String(needle)) => {
326 let haystack_str = haystack.as_str();
327 let needle_str = needle.as_str();
328
329 let result = match haystack_str.find(needle_str) {
331 Some(byte_pos) => {
332 haystack_str[..byte_pos].chars().count() as i64
334 }
335 None => -1,
336 };
337 unsafe { push(stack, Value::Int(result)) }
338 }
339 _ => panic!("string_find: expected two Strings on stack"),
340 }
341}
342
343#[unsafe(no_mangle)]
350pub unsafe extern "C" fn patch_seq_string_trim(stack: Stack) -> Stack {
351 assert!(!stack.is_null(), "string_trim: stack is empty");
352
353 let (stack, str_val) = unsafe { pop(stack) };
354
355 match str_val {
356 Value::String(s) => {
357 let trimmed = s.as_str().trim();
358 unsafe { push(stack, Value::String(global_string(trimmed.to_owned()))) }
359 }
360 _ => panic!("string_trim: expected String on stack"),
361 }
362}
363
364#[unsafe(no_mangle)]
371pub unsafe extern "C" fn patch_seq_string_to_upper(stack: Stack) -> Stack {
372 assert!(!stack.is_null(), "string_to_upper: stack is empty");
373
374 let (stack, str_val) = unsafe { pop(stack) };
375
376 match str_val {
377 Value::String(s) => {
378 let upper = s.as_str().to_uppercase();
379 unsafe { push(stack, Value::String(global_string(upper))) }
380 }
381 _ => panic!("string_to_upper: expected String on stack"),
382 }
383}
384
385#[unsafe(no_mangle)]
392pub unsafe extern "C" fn patch_seq_string_to_lower(stack: Stack) -> Stack {
393 assert!(!stack.is_null(), "string_to_lower: stack is empty");
394
395 let (stack, str_val) = unsafe { pop(stack) };
396
397 match str_val {
398 Value::String(s) => {
399 let lower = s.as_str().to_lowercase();
400 unsafe { push(stack, Value::String(global_string(lower))) }
401 }
402 _ => panic!("string_to_lower: expected String on stack"),
403 }
404}
405
406#[unsafe(no_mangle)]
413pub unsafe extern "C" fn patch_seq_string_equal(stack: Stack) -> Stack {
414 assert!(!stack.is_null(), "string_equal: stack is empty");
415
416 let (stack, str2_val) = unsafe { pop(stack) };
417 assert!(!stack.is_null(), "string_equal: need two strings");
418 let (stack, str1_val) = unsafe { pop(stack) };
419
420 match (str1_val, str2_val) {
421 (Value::String(s1), Value::String(s2)) => {
422 let equal = s1.as_str() == s2.as_str();
423 let result = if equal { 1 } else { 0 };
424 unsafe { push(stack, Value::Int(result)) }
425 }
426 _ => panic!("string_equal: expected two strings on stack"),
427 }
428}
429
430#[unsafe(no_mangle)]
447pub unsafe extern "C" fn patch_seq_json_escape(stack: Stack) -> Stack {
448 assert!(!stack.is_null(), "json_escape: stack is empty");
449
450 let (stack, value) = unsafe { pop(stack) };
451
452 match value {
453 Value::String(s) => {
454 let input = s.as_str();
455 let mut result = String::with_capacity(input.len() + 16);
456
457 for ch in input.chars() {
458 match ch {
459 '"' => result.push_str("\\\""),
460 '\\' => result.push_str("\\\\"),
461 '\n' => result.push_str("\\n"),
462 '\r' => result.push_str("\\r"),
463 '\t' => result.push_str("\\t"),
464 '\x08' => result.push_str("\\b"), '\x0C' => result.push_str("\\f"), c if c.is_control() => {
469 result.push_str(&format!("\\u{:04X}", c as u32));
470 }
471 c => result.push(c),
472 }
473 }
474
475 unsafe { push(stack, Value::String(global_string(result))) }
476 }
477 _ => panic!("json_escape: expected String on stack"),
478 }
479}
480
481#[unsafe(no_mangle)]
490pub unsafe extern "C" fn patch_seq_string_to_int(stack: Stack) -> Stack {
491 assert!(!stack.is_null(), "string->int: stack is empty");
492 let (stack, val) = unsafe { pop(stack) };
493
494 match val {
495 Value::String(s) => match s.as_str().trim().parse::<i64>() {
496 Ok(i) => {
497 let stack = unsafe { push(stack, Value::Int(i)) };
498 unsafe { push(stack, Value::Int(1)) }
499 }
500 Err(_) => {
501 let stack = unsafe { push(stack, Value::Int(0)) };
502 unsafe { push(stack, Value::Int(0)) }
503 }
504 },
505 _ => panic!("string->int: expected String on stack"),
506 }
507}
508
509#[unsafe(no_mangle)]
519pub unsafe extern "C" fn patch_seq_string_chomp(stack: Stack) -> Stack {
520 assert!(!stack.is_null(), "string_chomp: stack is empty");
521
522 let (stack, str_val) = unsafe { pop(stack) };
523
524 match str_val {
525 Value::String(s) => {
526 let mut result = s.as_str().to_owned();
527 if result.ends_with('\n') {
528 result.pop();
529 if result.ends_with('\r') {
530 result.pop();
531 }
532 }
533 unsafe { push(stack, Value::String(global_string(result))) }
534 }
535 _ => panic!("string_chomp: expected String on stack"),
536 }
537}
538
539pub use patch_seq_char_to_string as char_to_string;
541pub use patch_seq_json_escape as json_escape;
542pub use patch_seq_string_byte_length as string_byte_length;
543pub use patch_seq_string_char_at as string_char_at;
544pub use patch_seq_string_chomp as string_chomp;
545pub use patch_seq_string_concat as string_concat;
546pub use patch_seq_string_contains as string_contains;
547pub use patch_seq_string_empty as string_empty;
548pub use patch_seq_string_equal as string_equal;
549pub use patch_seq_string_find as string_find;
550pub use patch_seq_string_length as string_length;
551pub use patch_seq_string_split as string_split;
552pub use patch_seq_string_starts_with as string_starts_with;
553pub use patch_seq_string_substring as string_substring;
554pub use patch_seq_string_to_int as string_to_int;
555pub use patch_seq_string_to_lower as string_to_lower;
556pub use patch_seq_string_to_upper as string_to_upper;
557pub use patch_seq_string_trim as string_trim;
558
559#[unsafe(no_mangle)]
587pub unsafe extern "C" fn patch_seq_string_to_cstring(stack: Stack, _out: *mut u8) -> *mut u8 {
588 assert!(!stack.is_null(), "string_to_cstring: stack is empty");
589
590 let node = unsafe { &*stack };
592 match &node.value {
593 Value::String(s) => {
594 let bytes = s.as_str().as_bytes();
595 let len = bytes.len();
596
597 let alloc_size = len.checked_add(1).unwrap_or_else(|| {
599 panic!(
600 "string_to_cstring: string too large for C conversion (len={})",
601 len
602 )
603 });
604
605 let ptr = unsafe { libc::malloc(alloc_size) as *mut u8 };
607 if ptr.is_null() {
608 panic!("string_to_cstring: malloc failed");
609 }
610
611 unsafe {
613 std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, len);
614 *ptr.add(len) = 0;
616 }
617
618 ptr
619 }
620 _ => panic!("string_to_cstring: expected String on stack"),
621 }
622}
623
624#[unsafe(no_mangle)]
633pub unsafe extern "C" fn patch_seq_cstring_to_string(stack: Stack, cstr: *const u8) -> Stack {
634 if cstr.is_null() {
635 return unsafe { push(stack, Value::String(global_string(String::new()))) };
637 }
638
639 let len = unsafe { libc::strlen(cstr as *const libc::c_char) };
641
642 let slice = unsafe { std::slice::from_raw_parts(cstr, len) };
644 let s = String::from_utf8_lossy(slice).into_owned();
645
646 unsafe { push(stack, Value::String(global_string(s))) }
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 #[test]
654 fn test_string_split_simple() {
655 unsafe {
656 let stack = std::ptr::null_mut();
657 let stack = push(stack, Value::String(global_string("a b c".to_owned())));
658 let stack = push(stack, Value::String(global_string(" ".to_owned())));
659
660 let stack = string_split(stack);
661
662 let (stack, result) = pop(stack);
664 match result {
665 Value::Variant(v) => {
666 assert_eq!(v.tag, 0);
667 assert_eq!(v.fields.len(), 3);
668 assert_eq!(v.fields[0], Value::String(global_string("a".to_owned())));
669 assert_eq!(v.fields[1], Value::String(global_string("b".to_owned())));
670 assert_eq!(v.fields[2], Value::String(global_string("c".to_owned())));
671 }
672 _ => panic!("Expected Variant, got {:?}", result),
673 }
674
675 assert!(stack.is_null());
676 }
677 }
678
679 #[test]
680 fn test_string_split_empty() {
681 unsafe {
682 let stack = std::ptr::null_mut();
683 let stack = push(stack, Value::String(global_string("".to_owned())));
684 let stack = push(stack, Value::String(global_string(" ".to_owned())));
685
686 let stack = string_split(stack);
687
688 let (stack, result) = pop(stack);
690 match result {
691 Value::Variant(v) => {
692 assert_eq!(v.tag, 0);
693 assert_eq!(v.fields.len(), 1);
694 assert_eq!(v.fields[0], Value::String(global_string("".to_owned())));
695 }
696 _ => panic!("Expected Variant, got {:?}", result),
697 }
698
699 assert!(stack.is_null());
700 }
701 }
702
703 #[test]
704 fn test_string_empty_true() {
705 unsafe {
706 let stack = std::ptr::null_mut();
707 let stack = push(stack, Value::String(global_string("".to_owned())));
708
709 let stack = string_empty(stack);
710
711 let (stack, result) = pop(stack);
712 assert_eq!(result, Value::Int(1));
713 assert!(stack.is_null());
714 }
715 }
716
717 #[test]
718 fn test_string_empty_false() {
719 unsafe {
720 let stack = std::ptr::null_mut();
721 let stack = push(stack, Value::String(global_string("hello".to_owned())));
722
723 let stack = string_empty(stack);
724
725 let (stack, result) = pop(stack);
726 assert_eq!(result, Value::Int(0));
727 assert!(stack.is_null());
728 }
729 }
730
731 #[test]
732 fn test_string_contains_true() {
733 unsafe {
734 let stack = std::ptr::null_mut();
735 let stack = push(
736 stack,
737 Value::String(global_string("hello world".to_owned())),
738 );
739 let stack = push(stack, Value::String(global_string("world".to_owned())));
740
741 let stack = string_contains(stack);
742
743 let (stack, result) = pop(stack);
744 assert_eq!(result, Value::Int(1));
745 assert!(stack.is_null());
746 }
747 }
748
749 #[test]
750 fn test_string_contains_false() {
751 unsafe {
752 let stack = std::ptr::null_mut();
753 let stack = push(
754 stack,
755 Value::String(global_string("hello world".to_owned())),
756 );
757 let stack = push(stack, Value::String(global_string("foo".to_owned())));
758
759 let stack = string_contains(stack);
760
761 let (stack, result) = pop(stack);
762 assert_eq!(result, Value::Int(0));
763 assert!(stack.is_null());
764 }
765 }
766
767 #[test]
768 fn test_string_starts_with_true() {
769 unsafe {
770 let stack = std::ptr::null_mut();
771 let stack = push(
772 stack,
773 Value::String(global_string("hello world".to_owned())),
774 );
775 let stack = push(stack, Value::String(global_string("hello".to_owned())));
776
777 let stack = string_starts_with(stack);
778
779 let (stack, result) = pop(stack);
780 assert_eq!(result, Value::Int(1));
781 assert!(stack.is_null());
782 }
783 }
784
785 #[test]
786 fn test_string_starts_with_false() {
787 unsafe {
788 let stack = std::ptr::null_mut();
789 let stack = push(
790 stack,
791 Value::String(global_string("hello world".to_owned())),
792 );
793 let stack = push(stack, Value::String(global_string("world".to_owned())));
794
795 let stack = string_starts_with(stack);
796
797 let (stack, result) = pop(stack);
798 assert_eq!(result, Value::Int(0));
799 assert!(stack.is_null());
800 }
801 }
802
803 #[test]
804 fn test_http_request_line_parsing() {
805 unsafe {
807 let stack = std::ptr::null_mut();
808 let stack = push(
809 stack,
810 Value::String(global_string("GET /api/users HTTP/1.1".to_owned())),
811 );
812 let stack = push(stack, Value::String(global_string(" ".to_owned())));
813
814 let stack = string_split(stack);
815
816 let (stack, result) = pop(stack);
818 match result {
819 Value::Variant(v) => {
820 assert_eq!(v.tag, 0);
821 assert_eq!(v.fields.len(), 3);
822 assert_eq!(v.fields[0], Value::String(global_string("GET".to_owned())));
823 assert_eq!(
824 v.fields[1],
825 Value::String(global_string("/api/users".to_owned()))
826 );
827 assert_eq!(
828 v.fields[2],
829 Value::String(global_string("HTTP/1.1".to_owned()))
830 );
831 }
832 _ => panic!("Expected Variant, got {:?}", result),
833 }
834
835 assert!(stack.is_null());
836 }
837 }
838
839 #[test]
840 fn test_path_routing() {
841 unsafe {
843 let stack = std::ptr::null_mut();
844 let stack = push(stack, Value::String(global_string("/api/users".to_owned())));
845 let stack = push(stack, Value::String(global_string("/api/".to_owned())));
846
847 let stack = string_starts_with(stack);
848
849 let (stack, result) = pop(stack);
850 assert_eq!(result, Value::Int(1));
851 assert!(stack.is_null());
852 }
853 }
854
855 #[test]
856 fn test_string_concat() {
857 unsafe {
858 let stack = std::ptr::null_mut();
859 let stack = push(stack, Value::String(global_string("Hello, ".to_owned())));
860 let stack = push(stack, Value::String(global_string("World!".to_owned())));
861
862 let stack = string_concat(stack);
863
864 let (stack, result) = pop(stack);
865 assert_eq!(
866 result,
867 Value::String(global_string("Hello, World!".to_owned()))
868 );
869 assert!(stack.is_null());
870 }
871 }
872
873 #[test]
874 fn test_string_length() {
875 unsafe {
876 let stack = std::ptr::null_mut();
877 let stack = push(stack, Value::String(global_string("Hello".to_owned())));
878
879 let stack = string_length(stack);
880
881 let (stack, result) = pop(stack);
882 assert_eq!(result, Value::Int(5));
883 assert!(stack.is_null());
884 }
885 }
886
887 #[test]
888 fn test_string_length_empty() {
889 unsafe {
890 let stack = std::ptr::null_mut();
891 let stack = push(stack, Value::String(global_string("".to_owned())));
892
893 let stack = string_length(stack);
894
895 let (stack, result) = pop(stack);
896 assert_eq!(result, Value::Int(0));
897 assert!(stack.is_null());
898 }
899 }
900
901 #[test]
902 fn test_string_trim() {
903 unsafe {
904 let stack = std::ptr::null_mut();
905 let stack = push(
906 stack,
907 Value::String(global_string(" Hello, World! ".to_owned())),
908 );
909
910 let stack = string_trim(stack);
911
912 let (stack, result) = pop(stack);
913 assert_eq!(
914 result,
915 Value::String(global_string("Hello, World!".to_owned()))
916 );
917 assert!(stack.is_null());
918 }
919 }
920
921 #[test]
922 fn test_string_to_upper() {
923 unsafe {
924 let stack = std::ptr::null_mut();
925 let stack = push(
926 stack,
927 Value::String(global_string("Hello, World!".to_owned())),
928 );
929
930 let stack = string_to_upper(stack);
931
932 let (stack, result) = pop(stack);
933 assert_eq!(
934 result,
935 Value::String(global_string("HELLO, WORLD!".to_owned()))
936 );
937 assert!(stack.is_null());
938 }
939 }
940
941 #[test]
942 fn test_string_to_lower() {
943 unsafe {
944 let stack = std::ptr::null_mut();
945 let stack = push(
946 stack,
947 Value::String(global_string("Hello, World!".to_owned())),
948 );
949
950 let stack = string_to_lower(stack);
951
952 let (stack, result) = pop(stack);
953 assert_eq!(
954 result,
955 Value::String(global_string("hello, world!".to_owned()))
956 );
957 assert!(stack.is_null());
958 }
959 }
960
961 #[test]
962 fn test_http_header_content_length() {
963 unsafe {
965 let stack = std::ptr::null_mut();
966 let stack = push(
967 stack,
968 Value::String(global_string("Content-Length: ".to_owned())),
969 );
970 let stack = push(stack, Value::String(global_string("42".to_owned())));
971
972 let stack = string_concat(stack);
973
974 let (stack, result) = pop(stack);
975 assert_eq!(
976 result,
977 Value::String(global_string("Content-Length: 42".to_owned()))
978 );
979 assert!(stack.is_null());
980 }
981 }
982
983 #[test]
984 fn test_string_equal_true() {
985 unsafe {
986 let stack = std::ptr::null_mut();
987 let stack = push(stack, Value::String(global_string("hello".to_owned())));
988 let stack = push(stack, Value::String(global_string("hello".to_owned())));
989
990 let stack = string_equal(stack);
991
992 let (stack, result) = pop(stack);
993 assert_eq!(result, Value::Int(1));
994 assert!(stack.is_null());
995 }
996 }
997
998 #[test]
999 fn test_string_equal_false() {
1000 unsafe {
1001 let stack = std::ptr::null_mut();
1002 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1003 let stack = push(stack, Value::String(global_string("world".to_owned())));
1004
1005 let stack = string_equal(stack);
1006
1007 let (stack, result) = pop(stack);
1008 assert_eq!(result, Value::Int(0));
1009 assert!(stack.is_null());
1010 }
1011 }
1012
1013 #[test]
1014 fn test_string_equal_empty_strings() {
1015 unsafe {
1016 let stack = std::ptr::null_mut();
1017 let stack = push(stack, Value::String(global_string("".to_owned())));
1018 let stack = push(stack, Value::String(global_string("".to_owned())));
1019
1020 let stack = string_equal(stack);
1021
1022 let (stack, result) = pop(stack);
1023 assert_eq!(result, Value::Int(1));
1024 assert!(stack.is_null());
1025 }
1026 }
1027
1028 #[test]
1031 fn test_string_length_utf8() {
1032 unsafe {
1034 let stack = std::ptr::null_mut();
1035 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1036
1037 let stack = string_length(stack);
1038
1039 let (stack, result) = pop(stack);
1040 assert_eq!(result, Value::Int(5)); assert!(stack.is_null());
1042 }
1043 }
1044
1045 #[test]
1046 fn test_string_length_emoji() {
1047 unsafe {
1049 let stack = std::ptr::null_mut();
1050 let stack = push(stack, Value::String(global_string("hi🎉".to_owned())));
1051
1052 let stack = string_length(stack);
1053
1054 let (stack, result) = pop(stack);
1055 assert_eq!(result, Value::Int(3)); assert!(stack.is_null());
1057 }
1058 }
1059
1060 #[test]
1061 fn test_string_byte_length_ascii() {
1062 unsafe {
1063 let stack = std::ptr::null_mut();
1064 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1065
1066 let stack = string_byte_length(stack);
1067
1068 let (stack, result) = pop(stack);
1069 assert_eq!(result, Value::Int(5)); assert!(stack.is_null());
1071 }
1072 }
1073
1074 #[test]
1075 fn test_string_byte_length_utf8() {
1076 unsafe {
1078 let stack = std::ptr::null_mut();
1079 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1080
1081 let stack = string_byte_length(stack);
1082
1083 let (stack, result) = pop(stack);
1084 assert_eq!(result, Value::Int(6)); assert!(stack.is_null());
1086 }
1087 }
1088
1089 #[test]
1090 fn test_string_char_at_ascii() {
1091 unsafe {
1092 let stack = std::ptr::null_mut();
1093 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1094 let stack = push(stack, Value::Int(0));
1095
1096 let stack = string_char_at(stack);
1097
1098 let (stack, result) = pop(stack);
1099 assert_eq!(result, Value::Int(104)); assert!(stack.is_null());
1101 }
1102 }
1103
1104 #[test]
1105 fn test_string_char_at_utf8() {
1106 unsafe {
1108 let stack = std::ptr::null_mut();
1109 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1110 let stack = push(stack, Value::Int(1));
1111
1112 let stack = string_char_at(stack);
1113
1114 let (stack, result) = pop(stack);
1115 assert_eq!(result, Value::Int(233)); assert!(stack.is_null());
1117 }
1118 }
1119
1120 #[test]
1121 fn test_string_char_at_out_of_bounds() {
1122 unsafe {
1123 let stack = std::ptr::null_mut();
1124 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1125 let stack = push(stack, Value::Int(10)); let stack = string_char_at(stack);
1128
1129 let (stack, result) = pop(stack);
1130 assert_eq!(result, Value::Int(-1));
1131 assert!(stack.is_null());
1132 }
1133 }
1134
1135 #[test]
1136 fn test_string_char_at_negative() {
1137 unsafe {
1138 let stack = std::ptr::null_mut();
1139 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1140 let stack = push(stack, Value::Int(-1));
1141
1142 let stack = string_char_at(stack);
1143
1144 let (stack, result) = pop(stack);
1145 assert_eq!(result, Value::Int(-1));
1146 assert!(stack.is_null());
1147 }
1148 }
1149
1150 #[test]
1151 fn test_string_substring_simple() {
1152 unsafe {
1153 let stack = std::ptr::null_mut();
1154 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1155 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1159
1160 let (stack, result) = pop(stack);
1161 assert_eq!(result, Value::String(global_string("ell".to_owned())));
1162 assert!(stack.is_null());
1163 }
1164 }
1165
1166 #[test]
1167 fn test_string_substring_utf8() {
1168 unsafe {
1170 let stack = std::ptr::null_mut();
1171 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1172 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1176
1177 let (stack, result) = pop(stack);
1178 assert_eq!(result, Value::String(global_string("éll".to_owned())));
1179 assert!(stack.is_null());
1180 }
1181 }
1182
1183 #[test]
1184 fn test_string_substring_clamp() {
1185 unsafe {
1187 let stack = std::ptr::null_mut();
1188 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1189 let stack = push(stack, Value::Int(2)); let stack = push(stack, Value::Int(100)); let stack = string_substring(stack);
1193
1194 let (stack, result) = pop(stack);
1195 assert_eq!(result, Value::String(global_string("llo".to_owned())));
1196 assert!(stack.is_null());
1197 }
1198 }
1199
1200 #[test]
1201 fn test_string_substring_beyond_end() {
1202 unsafe {
1204 let stack = std::ptr::null_mut();
1205 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1206 let stack = push(stack, Value::Int(10)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1210
1211 let (stack, result) = pop(stack);
1212 assert_eq!(result, Value::String(global_string("".to_owned())));
1213 assert!(stack.is_null());
1214 }
1215 }
1216
1217 #[test]
1218 fn test_char_to_string_ascii() {
1219 unsafe {
1220 let stack = std::ptr::null_mut();
1221 let stack = push(stack, Value::Int(65)); let stack = char_to_string(stack);
1224
1225 let (stack, result) = pop(stack);
1226 assert_eq!(result, Value::String(global_string("A".to_owned())));
1227 assert!(stack.is_null());
1228 }
1229 }
1230
1231 #[test]
1232 fn test_char_to_string_utf8() {
1233 unsafe {
1234 let stack = std::ptr::null_mut();
1235 let stack = push(stack, Value::Int(233)); let stack = char_to_string(stack);
1238
1239 let (stack, result) = pop(stack);
1240 assert_eq!(result, Value::String(global_string("é".to_owned())));
1241 assert!(stack.is_null());
1242 }
1243 }
1244
1245 #[test]
1246 fn test_char_to_string_newline() {
1247 unsafe {
1248 let stack = std::ptr::null_mut();
1249 let stack = push(stack, Value::Int(10)); let stack = char_to_string(stack);
1252
1253 let (stack, result) = pop(stack);
1254 assert_eq!(result, Value::String(global_string("\n".to_owned())));
1255 assert!(stack.is_null());
1256 }
1257 }
1258
1259 #[test]
1260 fn test_char_to_string_invalid() {
1261 unsafe {
1262 let stack = std::ptr::null_mut();
1263 let stack = push(stack, Value::Int(-1)); let stack = char_to_string(stack);
1266
1267 let (stack, result) = pop(stack);
1268 assert_eq!(result, Value::String(global_string("".to_owned())));
1269 assert!(stack.is_null());
1270 }
1271 }
1272
1273 #[test]
1274 fn test_string_find_found() {
1275 unsafe {
1276 let stack = std::ptr::null_mut();
1277 let stack = push(
1278 stack,
1279 Value::String(global_string("hello world".to_owned())),
1280 );
1281 let stack = push(stack, Value::String(global_string("world".to_owned())));
1282
1283 let stack = string_find(stack);
1284
1285 let (stack, result) = pop(stack);
1286 assert_eq!(result, Value::Int(6)); assert!(stack.is_null());
1288 }
1289 }
1290
1291 #[test]
1292 fn test_string_find_not_found() {
1293 unsafe {
1294 let stack = std::ptr::null_mut();
1295 let stack = push(
1296 stack,
1297 Value::String(global_string("hello world".to_owned())),
1298 );
1299 let stack = push(stack, Value::String(global_string("xyz".to_owned())));
1300
1301 let stack = string_find(stack);
1302
1303 let (stack, result) = pop(stack);
1304 assert_eq!(result, Value::Int(-1));
1305 assert!(stack.is_null());
1306 }
1307 }
1308
1309 #[test]
1310 fn test_string_find_first_match() {
1311 unsafe {
1313 let stack = std::ptr::null_mut();
1314 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1315 let stack = push(stack, Value::String(global_string("l".to_owned())));
1316
1317 let stack = string_find(stack);
1318
1319 let (stack, result) = pop(stack);
1320 assert_eq!(result, Value::Int(2)); assert!(stack.is_null());
1322 }
1323 }
1324
1325 #[test]
1326 fn test_string_find_utf8() {
1327 unsafe {
1329 let stack = std::ptr::null_mut();
1330 let stack = push(
1331 stack,
1332 Value::String(global_string("héllo wörld".to_owned())),
1333 );
1334 let stack = push(stack, Value::String(global_string("wörld".to_owned())));
1335
1336 let stack = string_find(stack);
1337
1338 let (stack, result) = pop(stack);
1339 assert_eq!(result, Value::Int(6)); assert!(stack.is_null());
1341 }
1342 }
1343
1344 #[test]
1347 fn test_json_escape_quotes() {
1348 unsafe {
1349 let stack = std::ptr::null_mut();
1350 let stack = push(
1351 stack,
1352 Value::String(global_string("hello \"world\"".to_owned())),
1353 );
1354
1355 let stack = json_escape(stack);
1356
1357 let (stack, result) = pop(stack);
1358 assert_eq!(
1359 result,
1360 Value::String(global_string("hello \\\"world\\\"".to_owned()))
1361 );
1362 assert!(stack.is_null());
1363 }
1364 }
1365
1366 #[test]
1367 fn test_json_escape_backslash() {
1368 unsafe {
1369 let stack = std::ptr::null_mut();
1370 let stack = push(
1371 stack,
1372 Value::String(global_string("path\\to\\file".to_owned())),
1373 );
1374
1375 let stack = json_escape(stack);
1376
1377 let (stack, result) = pop(stack);
1378 assert_eq!(
1379 result,
1380 Value::String(global_string("path\\\\to\\\\file".to_owned()))
1381 );
1382 assert!(stack.is_null());
1383 }
1384 }
1385
1386 #[test]
1387 fn test_json_escape_newline_tab() {
1388 unsafe {
1389 let stack = std::ptr::null_mut();
1390 let stack = push(
1391 stack,
1392 Value::String(global_string("line1\nline2\ttabbed".to_owned())),
1393 );
1394
1395 let stack = json_escape(stack);
1396
1397 let (stack, result) = pop(stack);
1398 assert_eq!(
1399 result,
1400 Value::String(global_string("line1\\nline2\\ttabbed".to_owned()))
1401 );
1402 assert!(stack.is_null());
1403 }
1404 }
1405
1406 #[test]
1407 fn test_json_escape_carriage_return() {
1408 unsafe {
1409 let stack = std::ptr::null_mut();
1410 let stack = push(
1411 stack,
1412 Value::String(global_string("line1\r\nline2".to_owned())),
1413 );
1414
1415 let stack = json_escape(stack);
1416
1417 let (stack, result) = pop(stack);
1418 assert_eq!(
1419 result,
1420 Value::String(global_string("line1\\r\\nline2".to_owned()))
1421 );
1422 assert!(stack.is_null());
1423 }
1424 }
1425
1426 #[test]
1427 fn test_json_escape_control_chars() {
1428 unsafe {
1429 let stack = std::ptr::null_mut();
1430 let stack = push(
1432 stack,
1433 Value::String(global_string("a\x08b\x0Cc".to_owned())),
1434 );
1435
1436 let stack = json_escape(stack);
1437
1438 let (stack, result) = pop(stack);
1439 assert_eq!(result, Value::String(global_string("a\\bb\\fc".to_owned())));
1440 assert!(stack.is_null());
1441 }
1442 }
1443
1444 #[test]
1445 fn test_json_escape_unicode_control() {
1446 unsafe {
1447 let stack = std::ptr::null_mut();
1448 let stack = push(stack, Value::String(global_string("a\x00b".to_owned())));
1450
1451 let stack = json_escape(stack);
1452
1453 let (stack, result) = pop(stack);
1454 assert_eq!(result, Value::String(global_string("a\\u0000b".to_owned())));
1455 assert!(stack.is_null());
1456 }
1457 }
1458
1459 #[test]
1460 fn test_json_escape_mixed_special_chars() {
1461 unsafe {
1463 let stack = std::ptr::null_mut();
1464 let stack = push(
1465 stack,
1466 Value::String(global_string("Line 1\nLine \"2\"\ttab\r\n".to_owned())),
1467 );
1468
1469 let stack = json_escape(stack);
1470
1471 let (stack, result) = pop(stack);
1472 assert_eq!(
1473 result,
1474 Value::String(global_string(
1475 "Line 1\\nLine \\\"2\\\"\\ttab\\r\\n".to_owned()
1476 ))
1477 );
1478 assert!(stack.is_null());
1479 }
1480 }
1481
1482 #[test]
1483 fn test_json_escape_no_change() {
1484 unsafe {
1486 let stack = std::ptr::null_mut();
1487 let stack = push(
1488 stack,
1489 Value::String(global_string("Hello, World!".to_owned())),
1490 );
1491
1492 let stack = json_escape(stack);
1493
1494 let (stack, result) = pop(stack);
1495 assert_eq!(
1496 result,
1497 Value::String(global_string("Hello, World!".to_owned()))
1498 );
1499 assert!(stack.is_null());
1500 }
1501 }
1502
1503 #[test]
1504 fn test_json_escape_empty_string() {
1505 unsafe {
1506 let stack = std::ptr::null_mut();
1507 let stack = push(stack, Value::String(global_string("".to_owned())));
1508
1509 let stack = json_escape(stack);
1510
1511 let (stack, result) = pop(stack);
1512 assert_eq!(result, Value::String(global_string("".to_owned())));
1513 assert!(stack.is_null());
1514 }
1515 }
1516
1517 #[test]
1520 fn test_string_to_int_success() {
1521 unsafe {
1522 let stack = std::ptr::null_mut();
1523 let stack = push(stack, Value::String(global_string("42".to_owned())));
1524
1525 let stack = string_to_int(stack);
1526
1527 let (stack, success) = pop(stack);
1528 let (stack, value) = pop(stack);
1529 assert_eq!(success, Value::Int(1));
1530 assert_eq!(value, Value::Int(42));
1531 assert!(stack.is_null());
1532 }
1533 }
1534
1535 #[test]
1536 fn test_string_to_int_negative() {
1537 unsafe {
1538 let stack = std::ptr::null_mut();
1539 let stack = push(stack, Value::String(global_string("-99".to_owned())));
1540
1541 let stack = string_to_int(stack);
1542
1543 let (stack, success) = pop(stack);
1544 let (stack, value) = pop(stack);
1545 assert_eq!(success, Value::Int(1));
1546 assert_eq!(value, Value::Int(-99));
1547 assert!(stack.is_null());
1548 }
1549 }
1550
1551 #[test]
1552 fn test_string_to_int_with_whitespace() {
1553 unsafe {
1554 let stack = std::ptr::null_mut();
1555 let stack = push(stack, Value::String(global_string(" 123 ".to_owned())));
1556
1557 let stack = string_to_int(stack);
1558
1559 let (stack, success) = pop(stack);
1560 let (stack, value) = pop(stack);
1561 assert_eq!(success, Value::Int(1));
1562 assert_eq!(value, Value::Int(123));
1563 assert!(stack.is_null());
1564 }
1565 }
1566
1567 #[test]
1568 fn test_string_to_int_failure() {
1569 unsafe {
1570 let stack = std::ptr::null_mut();
1571 let stack = push(
1572 stack,
1573 Value::String(global_string("not a number".to_owned())),
1574 );
1575
1576 let stack = string_to_int(stack);
1577
1578 let (stack, success) = pop(stack);
1579 let (stack, value) = pop(stack);
1580 assert_eq!(success, Value::Int(0));
1581 assert_eq!(value, Value::Int(0));
1582 assert!(stack.is_null());
1583 }
1584 }
1585
1586 #[test]
1587 fn test_string_to_int_empty() {
1588 unsafe {
1589 let stack = std::ptr::null_mut();
1590 let stack = push(stack, Value::String(global_string("".to_owned())));
1591
1592 let stack = string_to_int(stack);
1593
1594 let (stack, success) = pop(stack);
1595 let (stack, value) = pop(stack);
1596 assert_eq!(success, Value::Int(0));
1597 assert_eq!(value, Value::Int(0));
1598 assert!(stack.is_null());
1599 }
1600 }
1601
1602 #[test]
1603 fn test_string_to_int_leading_zeros() {
1604 unsafe {
1605 let stack = std::ptr::null_mut();
1606 let stack = push(stack, Value::String(global_string("007".to_owned())));
1607
1608 let stack = string_to_int(stack);
1609
1610 let (stack, success) = pop(stack);
1611 let (stack, value) = pop(stack);
1612 assert_eq!(success, Value::Int(1));
1613 assert_eq!(value, Value::Int(7));
1614 assert!(stack.is_null());
1615 }
1616 }
1617}