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