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
509pub use patch_seq_char_to_string as char_to_string;
511pub use patch_seq_json_escape as json_escape;
512pub use patch_seq_string_byte_length as string_byte_length;
513pub use patch_seq_string_char_at as string_char_at;
514pub use patch_seq_string_concat as string_concat;
515pub use patch_seq_string_contains as string_contains;
516pub use patch_seq_string_empty as string_empty;
517pub use patch_seq_string_equal as string_equal;
518pub use patch_seq_string_find as string_find;
519pub use patch_seq_string_length as string_length;
520pub use patch_seq_string_split as string_split;
521pub use patch_seq_string_starts_with as string_starts_with;
522pub use patch_seq_string_substring as string_substring;
523pub use patch_seq_string_to_int as string_to_int;
524pub use patch_seq_string_to_lower as string_to_lower;
525pub use patch_seq_string_to_upper as string_to_upper;
526pub use patch_seq_string_trim as string_trim;
527
528#[cfg(test)]
529mod tests {
530 use super::*;
531
532 #[test]
533 fn test_string_split_simple() {
534 unsafe {
535 let stack = std::ptr::null_mut();
536 let stack = push(stack, Value::String(global_string("a b c".to_owned())));
537 let stack = push(stack, Value::String(global_string(" ".to_owned())));
538
539 let stack = string_split(stack);
540
541 let (stack, result) = pop(stack);
543 match result {
544 Value::Variant(v) => {
545 assert_eq!(v.tag, 0);
546 assert_eq!(v.fields.len(), 3);
547 assert_eq!(v.fields[0], Value::String(global_string("a".to_owned())));
548 assert_eq!(v.fields[1], Value::String(global_string("b".to_owned())));
549 assert_eq!(v.fields[2], Value::String(global_string("c".to_owned())));
550 }
551 _ => panic!("Expected Variant, got {:?}", result),
552 }
553
554 assert!(stack.is_null());
555 }
556 }
557
558 #[test]
559 fn test_string_split_empty() {
560 unsafe {
561 let stack = std::ptr::null_mut();
562 let stack = push(stack, Value::String(global_string("".to_owned())));
563 let stack = push(stack, Value::String(global_string(" ".to_owned())));
564
565 let stack = string_split(stack);
566
567 let (stack, result) = pop(stack);
569 match result {
570 Value::Variant(v) => {
571 assert_eq!(v.tag, 0);
572 assert_eq!(v.fields.len(), 1);
573 assert_eq!(v.fields[0], Value::String(global_string("".to_owned())));
574 }
575 _ => panic!("Expected Variant, got {:?}", result),
576 }
577
578 assert!(stack.is_null());
579 }
580 }
581
582 #[test]
583 fn test_string_empty_true() {
584 unsafe {
585 let stack = std::ptr::null_mut();
586 let stack = push(stack, Value::String(global_string("".to_owned())));
587
588 let stack = string_empty(stack);
589
590 let (stack, result) = pop(stack);
591 assert_eq!(result, Value::Int(1));
592 assert!(stack.is_null());
593 }
594 }
595
596 #[test]
597 fn test_string_empty_false() {
598 unsafe {
599 let stack = std::ptr::null_mut();
600 let stack = push(stack, Value::String(global_string("hello".to_owned())));
601
602 let stack = string_empty(stack);
603
604 let (stack, result) = pop(stack);
605 assert_eq!(result, Value::Int(0));
606 assert!(stack.is_null());
607 }
608 }
609
610 #[test]
611 fn test_string_contains_true() {
612 unsafe {
613 let stack = std::ptr::null_mut();
614 let stack = push(
615 stack,
616 Value::String(global_string("hello world".to_owned())),
617 );
618 let stack = push(stack, Value::String(global_string("world".to_owned())));
619
620 let stack = string_contains(stack);
621
622 let (stack, result) = pop(stack);
623 assert_eq!(result, Value::Int(1));
624 assert!(stack.is_null());
625 }
626 }
627
628 #[test]
629 fn test_string_contains_false() {
630 unsafe {
631 let stack = std::ptr::null_mut();
632 let stack = push(
633 stack,
634 Value::String(global_string("hello world".to_owned())),
635 );
636 let stack = push(stack, Value::String(global_string("foo".to_owned())));
637
638 let stack = string_contains(stack);
639
640 let (stack, result) = pop(stack);
641 assert_eq!(result, Value::Int(0));
642 assert!(stack.is_null());
643 }
644 }
645
646 #[test]
647 fn test_string_starts_with_true() {
648 unsafe {
649 let stack = std::ptr::null_mut();
650 let stack = push(
651 stack,
652 Value::String(global_string("hello world".to_owned())),
653 );
654 let stack = push(stack, Value::String(global_string("hello".to_owned())));
655
656 let stack = string_starts_with(stack);
657
658 let (stack, result) = pop(stack);
659 assert_eq!(result, Value::Int(1));
660 assert!(stack.is_null());
661 }
662 }
663
664 #[test]
665 fn test_string_starts_with_false() {
666 unsafe {
667 let stack = std::ptr::null_mut();
668 let stack = push(
669 stack,
670 Value::String(global_string("hello world".to_owned())),
671 );
672 let stack = push(stack, Value::String(global_string("world".to_owned())));
673
674 let stack = string_starts_with(stack);
675
676 let (stack, result) = pop(stack);
677 assert_eq!(result, Value::Int(0));
678 assert!(stack.is_null());
679 }
680 }
681
682 #[test]
683 fn test_http_request_line_parsing() {
684 unsafe {
686 let stack = std::ptr::null_mut();
687 let stack = push(
688 stack,
689 Value::String(global_string("GET /api/users HTTP/1.1".to_owned())),
690 );
691 let stack = push(stack, Value::String(global_string(" ".to_owned())));
692
693 let stack = string_split(stack);
694
695 let (stack, result) = pop(stack);
697 match result {
698 Value::Variant(v) => {
699 assert_eq!(v.tag, 0);
700 assert_eq!(v.fields.len(), 3);
701 assert_eq!(v.fields[0], Value::String(global_string("GET".to_owned())));
702 assert_eq!(
703 v.fields[1],
704 Value::String(global_string("/api/users".to_owned()))
705 );
706 assert_eq!(
707 v.fields[2],
708 Value::String(global_string("HTTP/1.1".to_owned()))
709 );
710 }
711 _ => panic!("Expected Variant, got {:?}", result),
712 }
713
714 assert!(stack.is_null());
715 }
716 }
717
718 #[test]
719 fn test_path_routing() {
720 unsafe {
722 let stack = std::ptr::null_mut();
723 let stack = push(stack, Value::String(global_string("/api/users".to_owned())));
724 let stack = push(stack, Value::String(global_string("/api/".to_owned())));
725
726 let stack = string_starts_with(stack);
727
728 let (stack, result) = pop(stack);
729 assert_eq!(result, Value::Int(1));
730 assert!(stack.is_null());
731 }
732 }
733
734 #[test]
735 fn test_string_concat() {
736 unsafe {
737 let stack = std::ptr::null_mut();
738 let stack = push(stack, Value::String(global_string("Hello, ".to_owned())));
739 let stack = push(stack, Value::String(global_string("World!".to_owned())));
740
741 let stack = string_concat(stack);
742
743 let (stack, result) = pop(stack);
744 assert_eq!(
745 result,
746 Value::String(global_string("Hello, World!".to_owned()))
747 );
748 assert!(stack.is_null());
749 }
750 }
751
752 #[test]
753 fn test_string_length() {
754 unsafe {
755 let stack = std::ptr::null_mut();
756 let stack = push(stack, Value::String(global_string("Hello".to_owned())));
757
758 let stack = string_length(stack);
759
760 let (stack, result) = pop(stack);
761 assert_eq!(result, Value::Int(5));
762 assert!(stack.is_null());
763 }
764 }
765
766 #[test]
767 fn test_string_length_empty() {
768 unsafe {
769 let stack = std::ptr::null_mut();
770 let stack = push(stack, Value::String(global_string("".to_owned())));
771
772 let stack = string_length(stack);
773
774 let (stack, result) = pop(stack);
775 assert_eq!(result, Value::Int(0));
776 assert!(stack.is_null());
777 }
778 }
779
780 #[test]
781 fn test_string_trim() {
782 unsafe {
783 let stack = std::ptr::null_mut();
784 let stack = push(
785 stack,
786 Value::String(global_string(" Hello, World! ".to_owned())),
787 );
788
789 let stack = string_trim(stack);
790
791 let (stack, result) = pop(stack);
792 assert_eq!(
793 result,
794 Value::String(global_string("Hello, World!".to_owned()))
795 );
796 assert!(stack.is_null());
797 }
798 }
799
800 #[test]
801 fn test_string_to_upper() {
802 unsafe {
803 let stack = std::ptr::null_mut();
804 let stack = push(
805 stack,
806 Value::String(global_string("Hello, World!".to_owned())),
807 );
808
809 let stack = string_to_upper(stack);
810
811 let (stack, result) = pop(stack);
812 assert_eq!(
813 result,
814 Value::String(global_string("HELLO, WORLD!".to_owned()))
815 );
816 assert!(stack.is_null());
817 }
818 }
819
820 #[test]
821 fn test_string_to_lower() {
822 unsafe {
823 let stack = std::ptr::null_mut();
824 let stack = push(
825 stack,
826 Value::String(global_string("Hello, World!".to_owned())),
827 );
828
829 let stack = string_to_lower(stack);
830
831 let (stack, result) = pop(stack);
832 assert_eq!(
833 result,
834 Value::String(global_string("hello, world!".to_owned()))
835 );
836 assert!(stack.is_null());
837 }
838 }
839
840 #[test]
841 fn test_http_header_content_length() {
842 unsafe {
844 let stack = std::ptr::null_mut();
845 let stack = push(
846 stack,
847 Value::String(global_string("Content-Length: ".to_owned())),
848 );
849 let stack = push(stack, Value::String(global_string("42".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("Content-Length: 42".to_owned()))
857 );
858 assert!(stack.is_null());
859 }
860 }
861
862 #[test]
863 fn test_string_equal_true() {
864 unsafe {
865 let stack = std::ptr::null_mut();
866 let stack = push(stack, Value::String(global_string("hello".to_owned())));
867 let stack = push(stack, Value::String(global_string("hello".to_owned())));
868
869 let stack = string_equal(stack);
870
871 let (stack, result) = pop(stack);
872 assert_eq!(result, Value::Int(1));
873 assert!(stack.is_null());
874 }
875 }
876
877 #[test]
878 fn test_string_equal_false() {
879 unsafe {
880 let stack = std::ptr::null_mut();
881 let stack = push(stack, Value::String(global_string("hello".to_owned())));
882 let stack = push(stack, Value::String(global_string("world".to_owned())));
883
884 let stack = string_equal(stack);
885
886 let (stack, result) = pop(stack);
887 assert_eq!(result, Value::Int(0));
888 assert!(stack.is_null());
889 }
890 }
891
892 #[test]
893 fn test_string_equal_empty_strings() {
894 unsafe {
895 let stack = std::ptr::null_mut();
896 let stack = push(stack, Value::String(global_string("".to_owned())));
897 let stack = push(stack, Value::String(global_string("".to_owned())));
898
899 let stack = string_equal(stack);
900
901 let (stack, result) = pop(stack);
902 assert_eq!(result, Value::Int(1));
903 assert!(stack.is_null());
904 }
905 }
906
907 #[test]
910 fn test_string_length_utf8() {
911 unsafe {
913 let stack = std::ptr::null_mut();
914 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
915
916 let stack = string_length(stack);
917
918 let (stack, result) = pop(stack);
919 assert_eq!(result, Value::Int(5)); assert!(stack.is_null());
921 }
922 }
923
924 #[test]
925 fn test_string_length_emoji() {
926 unsafe {
928 let stack = std::ptr::null_mut();
929 let stack = push(stack, Value::String(global_string("hi🎉".to_owned())));
930
931 let stack = string_length(stack);
932
933 let (stack, result) = pop(stack);
934 assert_eq!(result, Value::Int(3)); assert!(stack.is_null());
936 }
937 }
938
939 #[test]
940 fn test_string_byte_length_ascii() {
941 unsafe {
942 let stack = std::ptr::null_mut();
943 let stack = push(stack, Value::String(global_string("hello".to_owned())));
944
945 let stack = string_byte_length(stack);
946
947 let (stack, result) = pop(stack);
948 assert_eq!(result, Value::Int(5)); assert!(stack.is_null());
950 }
951 }
952
953 #[test]
954 fn test_string_byte_length_utf8() {
955 unsafe {
957 let stack = std::ptr::null_mut();
958 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
959
960 let stack = string_byte_length(stack);
961
962 let (stack, result) = pop(stack);
963 assert_eq!(result, Value::Int(6)); assert!(stack.is_null());
965 }
966 }
967
968 #[test]
969 fn test_string_char_at_ascii() {
970 unsafe {
971 let stack = std::ptr::null_mut();
972 let stack = push(stack, Value::String(global_string("hello".to_owned())));
973 let stack = push(stack, Value::Int(0));
974
975 let stack = string_char_at(stack);
976
977 let (stack, result) = pop(stack);
978 assert_eq!(result, Value::Int(104)); assert!(stack.is_null());
980 }
981 }
982
983 #[test]
984 fn test_string_char_at_utf8() {
985 unsafe {
987 let stack = std::ptr::null_mut();
988 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
989 let stack = push(stack, Value::Int(1));
990
991 let stack = string_char_at(stack);
992
993 let (stack, result) = pop(stack);
994 assert_eq!(result, Value::Int(233)); assert!(stack.is_null());
996 }
997 }
998
999 #[test]
1000 fn test_string_char_at_out_of_bounds() {
1001 unsafe {
1002 let stack = std::ptr::null_mut();
1003 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1004 let stack = push(stack, Value::Int(10)); let stack = string_char_at(stack);
1007
1008 let (stack, result) = pop(stack);
1009 assert_eq!(result, Value::Int(-1));
1010 assert!(stack.is_null());
1011 }
1012 }
1013
1014 #[test]
1015 fn test_string_char_at_negative() {
1016 unsafe {
1017 let stack = std::ptr::null_mut();
1018 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1019 let stack = push(stack, Value::Int(-1));
1020
1021 let stack = string_char_at(stack);
1022
1023 let (stack, result) = pop(stack);
1024 assert_eq!(result, Value::Int(-1));
1025 assert!(stack.is_null());
1026 }
1027 }
1028
1029 #[test]
1030 fn test_string_substring_simple() {
1031 unsafe {
1032 let stack = std::ptr::null_mut();
1033 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1034 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1038
1039 let (stack, result) = pop(stack);
1040 assert_eq!(result, Value::String(global_string("ell".to_owned())));
1041 assert!(stack.is_null());
1042 }
1043 }
1044
1045 #[test]
1046 fn test_string_substring_utf8() {
1047 unsafe {
1049 let stack = std::ptr::null_mut();
1050 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1051 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1055
1056 let (stack, result) = pop(stack);
1057 assert_eq!(result, Value::String(global_string("éll".to_owned())));
1058 assert!(stack.is_null());
1059 }
1060 }
1061
1062 #[test]
1063 fn test_string_substring_clamp() {
1064 unsafe {
1066 let stack = std::ptr::null_mut();
1067 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1068 let stack = push(stack, Value::Int(2)); let stack = push(stack, Value::Int(100)); let stack = string_substring(stack);
1072
1073 let (stack, result) = pop(stack);
1074 assert_eq!(result, Value::String(global_string("llo".to_owned())));
1075 assert!(stack.is_null());
1076 }
1077 }
1078
1079 #[test]
1080 fn test_string_substring_beyond_end() {
1081 unsafe {
1083 let stack = std::ptr::null_mut();
1084 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1085 let stack = push(stack, Value::Int(10)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1089
1090 let (stack, result) = pop(stack);
1091 assert_eq!(result, Value::String(global_string("".to_owned())));
1092 assert!(stack.is_null());
1093 }
1094 }
1095
1096 #[test]
1097 fn test_char_to_string_ascii() {
1098 unsafe {
1099 let stack = std::ptr::null_mut();
1100 let stack = push(stack, Value::Int(65)); let stack = char_to_string(stack);
1103
1104 let (stack, result) = pop(stack);
1105 assert_eq!(result, Value::String(global_string("A".to_owned())));
1106 assert!(stack.is_null());
1107 }
1108 }
1109
1110 #[test]
1111 fn test_char_to_string_utf8() {
1112 unsafe {
1113 let stack = std::ptr::null_mut();
1114 let stack = push(stack, Value::Int(233)); let stack = char_to_string(stack);
1117
1118 let (stack, result) = pop(stack);
1119 assert_eq!(result, Value::String(global_string("é".to_owned())));
1120 assert!(stack.is_null());
1121 }
1122 }
1123
1124 #[test]
1125 fn test_char_to_string_newline() {
1126 unsafe {
1127 let stack = std::ptr::null_mut();
1128 let stack = push(stack, Value::Int(10)); let stack = char_to_string(stack);
1131
1132 let (stack, result) = pop(stack);
1133 assert_eq!(result, Value::String(global_string("\n".to_owned())));
1134 assert!(stack.is_null());
1135 }
1136 }
1137
1138 #[test]
1139 fn test_char_to_string_invalid() {
1140 unsafe {
1141 let stack = std::ptr::null_mut();
1142 let stack = push(stack, Value::Int(-1)); let stack = char_to_string(stack);
1145
1146 let (stack, result) = pop(stack);
1147 assert_eq!(result, Value::String(global_string("".to_owned())));
1148 assert!(stack.is_null());
1149 }
1150 }
1151
1152 #[test]
1153 fn test_string_find_found() {
1154 unsafe {
1155 let stack = std::ptr::null_mut();
1156 let stack = push(
1157 stack,
1158 Value::String(global_string("hello world".to_owned())),
1159 );
1160 let stack = push(stack, Value::String(global_string("world".to_owned())));
1161
1162 let stack = string_find(stack);
1163
1164 let (stack, result) = pop(stack);
1165 assert_eq!(result, Value::Int(6)); assert!(stack.is_null());
1167 }
1168 }
1169
1170 #[test]
1171 fn test_string_find_not_found() {
1172 unsafe {
1173 let stack = std::ptr::null_mut();
1174 let stack = push(
1175 stack,
1176 Value::String(global_string("hello world".to_owned())),
1177 );
1178 let stack = push(stack, Value::String(global_string("xyz".to_owned())));
1179
1180 let stack = string_find(stack);
1181
1182 let (stack, result) = pop(stack);
1183 assert_eq!(result, Value::Int(-1));
1184 assert!(stack.is_null());
1185 }
1186 }
1187
1188 #[test]
1189 fn test_string_find_first_match() {
1190 unsafe {
1192 let stack = std::ptr::null_mut();
1193 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1194 let stack = push(stack, Value::String(global_string("l".to_owned())));
1195
1196 let stack = string_find(stack);
1197
1198 let (stack, result) = pop(stack);
1199 assert_eq!(result, Value::Int(2)); assert!(stack.is_null());
1201 }
1202 }
1203
1204 #[test]
1205 fn test_string_find_utf8() {
1206 unsafe {
1208 let stack = std::ptr::null_mut();
1209 let stack = push(
1210 stack,
1211 Value::String(global_string("héllo wörld".to_owned())),
1212 );
1213 let stack = push(stack, Value::String(global_string("wörld".to_owned())));
1214
1215 let stack = string_find(stack);
1216
1217 let (stack, result) = pop(stack);
1218 assert_eq!(result, Value::Int(6)); assert!(stack.is_null());
1220 }
1221 }
1222
1223 #[test]
1226 fn test_json_escape_quotes() {
1227 unsafe {
1228 let stack = std::ptr::null_mut();
1229 let stack = push(
1230 stack,
1231 Value::String(global_string("hello \"world\"".to_owned())),
1232 );
1233
1234 let stack = json_escape(stack);
1235
1236 let (stack, result) = pop(stack);
1237 assert_eq!(
1238 result,
1239 Value::String(global_string("hello \\\"world\\\"".to_owned()))
1240 );
1241 assert!(stack.is_null());
1242 }
1243 }
1244
1245 #[test]
1246 fn test_json_escape_backslash() {
1247 unsafe {
1248 let stack = std::ptr::null_mut();
1249 let stack = push(
1250 stack,
1251 Value::String(global_string("path\\to\\file".to_owned())),
1252 );
1253
1254 let stack = json_escape(stack);
1255
1256 let (stack, result) = pop(stack);
1257 assert_eq!(
1258 result,
1259 Value::String(global_string("path\\\\to\\\\file".to_owned()))
1260 );
1261 assert!(stack.is_null());
1262 }
1263 }
1264
1265 #[test]
1266 fn test_json_escape_newline_tab() {
1267 unsafe {
1268 let stack = std::ptr::null_mut();
1269 let stack = push(
1270 stack,
1271 Value::String(global_string("line1\nline2\ttabbed".to_owned())),
1272 );
1273
1274 let stack = json_escape(stack);
1275
1276 let (stack, result) = pop(stack);
1277 assert_eq!(
1278 result,
1279 Value::String(global_string("line1\\nline2\\ttabbed".to_owned()))
1280 );
1281 assert!(stack.is_null());
1282 }
1283 }
1284
1285 #[test]
1286 fn test_json_escape_carriage_return() {
1287 unsafe {
1288 let stack = std::ptr::null_mut();
1289 let stack = push(
1290 stack,
1291 Value::String(global_string("line1\r\nline2".to_owned())),
1292 );
1293
1294 let stack = json_escape(stack);
1295
1296 let (stack, result) = pop(stack);
1297 assert_eq!(
1298 result,
1299 Value::String(global_string("line1\\r\\nline2".to_owned()))
1300 );
1301 assert!(stack.is_null());
1302 }
1303 }
1304
1305 #[test]
1306 fn test_json_escape_control_chars() {
1307 unsafe {
1308 let stack = std::ptr::null_mut();
1309 let stack = push(
1311 stack,
1312 Value::String(global_string("a\x08b\x0Cc".to_owned())),
1313 );
1314
1315 let stack = json_escape(stack);
1316
1317 let (stack, result) = pop(stack);
1318 assert_eq!(result, Value::String(global_string("a\\bb\\fc".to_owned())));
1319 assert!(stack.is_null());
1320 }
1321 }
1322
1323 #[test]
1324 fn test_json_escape_unicode_control() {
1325 unsafe {
1326 let stack = std::ptr::null_mut();
1327 let stack = push(stack, Value::String(global_string("a\x00b".to_owned())));
1329
1330 let stack = json_escape(stack);
1331
1332 let (stack, result) = pop(stack);
1333 assert_eq!(result, Value::String(global_string("a\\u0000b".to_owned())));
1334 assert!(stack.is_null());
1335 }
1336 }
1337
1338 #[test]
1339 fn test_json_escape_mixed_special_chars() {
1340 unsafe {
1342 let stack = std::ptr::null_mut();
1343 let stack = push(
1344 stack,
1345 Value::String(global_string("Line 1\nLine \"2\"\ttab\r\n".to_owned())),
1346 );
1347
1348 let stack = json_escape(stack);
1349
1350 let (stack, result) = pop(stack);
1351 assert_eq!(
1352 result,
1353 Value::String(global_string(
1354 "Line 1\\nLine \\\"2\\\"\\ttab\\r\\n".to_owned()
1355 ))
1356 );
1357 assert!(stack.is_null());
1358 }
1359 }
1360
1361 #[test]
1362 fn test_json_escape_no_change() {
1363 unsafe {
1365 let stack = std::ptr::null_mut();
1366 let stack = push(
1367 stack,
1368 Value::String(global_string("Hello, World!".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("Hello, World!".to_owned()))
1377 );
1378 assert!(stack.is_null());
1379 }
1380 }
1381
1382 #[test]
1383 fn test_json_escape_empty_string() {
1384 unsafe {
1385 let stack = std::ptr::null_mut();
1386 let stack = push(stack, Value::String(global_string("".to_owned())));
1387
1388 let stack = json_escape(stack);
1389
1390 let (stack, result) = pop(stack);
1391 assert_eq!(result, Value::String(global_string("".to_owned())));
1392 assert!(stack.is_null());
1393 }
1394 }
1395
1396 #[test]
1399 fn test_string_to_int_success() {
1400 unsafe {
1401 let stack = std::ptr::null_mut();
1402 let stack = push(stack, Value::String(global_string("42".to_owned())));
1403
1404 let stack = string_to_int(stack);
1405
1406 let (stack, success) = pop(stack);
1407 let (stack, value) = pop(stack);
1408 assert_eq!(success, Value::Int(1));
1409 assert_eq!(value, Value::Int(42));
1410 assert!(stack.is_null());
1411 }
1412 }
1413
1414 #[test]
1415 fn test_string_to_int_negative() {
1416 unsafe {
1417 let stack = std::ptr::null_mut();
1418 let stack = push(stack, Value::String(global_string("-99".to_owned())));
1419
1420 let stack = string_to_int(stack);
1421
1422 let (stack, success) = pop(stack);
1423 let (stack, value) = pop(stack);
1424 assert_eq!(success, Value::Int(1));
1425 assert_eq!(value, Value::Int(-99));
1426 assert!(stack.is_null());
1427 }
1428 }
1429
1430 #[test]
1431 fn test_string_to_int_with_whitespace() {
1432 unsafe {
1433 let stack = std::ptr::null_mut();
1434 let stack = push(stack, Value::String(global_string(" 123 ".to_owned())));
1435
1436 let stack = string_to_int(stack);
1437
1438 let (stack, success) = pop(stack);
1439 let (stack, value) = pop(stack);
1440 assert_eq!(success, Value::Int(1));
1441 assert_eq!(value, Value::Int(123));
1442 assert!(stack.is_null());
1443 }
1444 }
1445
1446 #[test]
1447 fn test_string_to_int_failure() {
1448 unsafe {
1449 let stack = std::ptr::null_mut();
1450 let stack = push(
1451 stack,
1452 Value::String(global_string("not a number".to_owned())),
1453 );
1454
1455 let stack = string_to_int(stack);
1456
1457 let (stack, success) = pop(stack);
1458 let (stack, value) = pop(stack);
1459 assert_eq!(success, Value::Int(0));
1460 assert_eq!(value, Value::Int(0));
1461 assert!(stack.is_null());
1462 }
1463 }
1464
1465 #[test]
1466 fn test_string_to_int_empty() {
1467 unsafe {
1468 let stack = std::ptr::null_mut();
1469 let stack = push(stack, Value::String(global_string("".to_owned())));
1470
1471 let stack = string_to_int(stack);
1472
1473 let (stack, success) = pop(stack);
1474 let (stack, value) = pop(stack);
1475 assert_eq!(success, Value::Int(0));
1476 assert_eq!(value, Value::Int(0));
1477 assert!(stack.is_null());
1478 }
1479 }
1480
1481 #[test]
1482 fn test_string_to_int_leading_zeros() {
1483 unsafe {
1484 let stack = std::ptr::null_mut();
1485 let stack = push(stack, Value::String(global_string("007".to_owned())));
1486
1487 let stack = string_to_int(stack);
1488
1489 let (stack, success) = pop(stack);
1490 let (stack, value) = pop(stack);
1491 assert_eq!(success, Value::Int(1));
1492 assert_eq!(value, Value::Int(7));
1493 assert!(stack.is_null());
1494 }
1495 }
1496}