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