1use crate::error::set_runtime_error;
14use crate::seqstring::global_string;
15use crate::stack::{Stack, pop, push};
16use crate::value::Value;
17use std::sync::Arc;
18
19#[unsafe(no_mangle)]
28pub unsafe extern "C" fn patch_seq_string_split(stack: Stack) -> Stack {
29 use crate::value::VariantData;
30
31 assert!(!stack.is_null(), "string_split: stack is empty");
32
33 let (stack, delim_val) = unsafe { pop(stack) };
34 assert!(!stack.is_null(), "string_split: need two strings");
35 let (stack, str_val) = unsafe { pop(stack) };
36
37 match (str_val, delim_val) {
38 (Value::String(s), Value::String(d)) => {
39 let fields: Vec<Value> = s
41 .as_str()
42 .split(d.as_str())
43 .map(|part| Value::String(global_string(part.to_owned())))
44 .collect();
45
46 let variant = Value::Variant(Arc::new(VariantData::new(
48 global_string("List".to_string()),
49 fields,
50 )));
51
52 unsafe { push(stack, variant) }
53 }
54 _ => panic!("string_split: expected two strings on stack"),
55 }
56}
57
58#[unsafe(no_mangle)]
65pub unsafe extern "C" fn patch_seq_string_empty(stack: Stack) -> Stack {
66 assert!(!stack.is_null(), "string_empty: stack is empty");
67
68 let (stack, value) = unsafe { pop(stack) };
69
70 match value {
71 Value::String(s) => {
72 let is_empty = s.as_str().is_empty();
73 unsafe { push(stack, Value::Bool(is_empty)) }
74 }
75 _ => panic!("string_empty: expected String on stack"),
76 }
77}
78
79#[unsafe(no_mangle)]
86pub unsafe extern "C" fn patch_seq_string_contains(stack: Stack) -> Stack {
87 assert!(!stack.is_null(), "string_contains: stack is empty");
88
89 let (stack, substring_val) = unsafe { pop(stack) };
90 assert!(!stack.is_null(), "string_contains: need two strings");
91 let (stack, str_val) = unsafe { pop(stack) };
92
93 match (str_val, substring_val) {
94 (Value::String(s), Value::String(sub)) => {
95 let contains = s.as_str().contains(sub.as_str());
96 unsafe { push(stack, Value::Bool(contains)) }
97 }
98 _ => panic!("string_contains: expected two strings on stack"),
99 }
100}
101
102#[unsafe(no_mangle)]
109pub unsafe extern "C" fn patch_seq_string_starts_with(stack: Stack) -> Stack {
110 assert!(!stack.is_null(), "string_starts_with: stack is empty");
111
112 let (stack, prefix_val) = unsafe { pop(stack) };
113 assert!(!stack.is_null(), "string_starts_with: need two strings");
114 let (stack, str_val) = unsafe { pop(stack) };
115
116 match (str_val, prefix_val) {
117 (Value::String(s), Value::String(prefix)) => {
118 let starts = s.as_str().starts_with(prefix.as_str());
119 unsafe { push(stack, Value::Bool(starts)) }
120 }
121 _ => panic!("string_starts_with: expected two strings on stack"),
122 }
123}
124
125#[unsafe(no_mangle)]
132pub unsafe extern "C" fn patch_seq_string_concat(stack: Stack) -> Stack {
133 assert!(!stack.is_null(), "string_concat: stack is empty");
134
135 let (stack, str2_val) = unsafe { pop(stack) };
136 assert!(!stack.is_null(), "string_concat: need two strings");
137 let (stack, str1_val) = unsafe { pop(stack) };
138
139 match (str1_val, str2_val) {
140 (Value::String(s1), Value::String(s2)) => {
141 let result = format!("{}{}", s1.as_str(), s2.as_str());
142 unsafe { push(stack, Value::String(global_string(result))) }
143 }
144 _ => panic!("string_concat: expected two strings on stack"),
145 }
146}
147
148#[unsafe(no_mangle)]
158pub unsafe extern "C" fn patch_seq_string_length(stack: Stack) -> Stack {
159 assert!(!stack.is_null(), "string_length: stack is empty");
160
161 let (stack, str_val) = unsafe { pop(stack) };
162
163 match str_val {
164 Value::String(s) => {
165 let len = s.as_str().chars().count() as i64;
166 unsafe { push(stack, Value::Int(len)) }
167 }
168 _ => panic!("string_length: expected String on stack"),
169 }
170}
171
172#[unsafe(no_mangle)]
181pub unsafe extern "C" fn patch_seq_string_byte_length(stack: Stack) -> Stack {
182 assert!(!stack.is_null(), "string_byte_length: stack is empty");
183
184 let (stack, str_val) = unsafe { pop(stack) };
185
186 match str_val {
187 Value::String(s) => {
188 let len = s.as_str().len() as i64;
189 unsafe { push(stack, Value::Int(len)) }
190 }
191 _ => panic!("string_byte_length: expected String on stack"),
192 }
193}
194
195#[unsafe(no_mangle)]
205pub unsafe extern "C" fn patch_seq_string_char_at(stack: Stack) -> Stack {
206 assert!(!stack.is_null(), "string_char_at: stack is empty");
207
208 let (stack, index_val) = unsafe { pop(stack) };
209 assert!(!stack.is_null(), "string_char_at: need string and index");
210 let (stack, str_val) = unsafe { pop(stack) };
211
212 match (str_val, index_val) {
213 (Value::String(s), Value::Int(index)) => {
214 let result = if index < 0 {
215 -1
216 } else {
217 s.as_str()
218 .chars()
219 .nth(index as usize)
220 .map(|c| c as i64)
221 .unwrap_or(-1)
222 };
223 unsafe { push(stack, Value::Int(result)) }
224 }
225 _ => panic!("string_char_at: expected String and Int on stack"),
226 }
227}
228
229#[unsafe(no_mangle)]
245pub unsafe extern "C" fn patch_seq_string_substring(stack: Stack) -> Stack {
246 assert!(!stack.is_null(), "string_substring: stack is empty");
247
248 let (stack, len_val) = unsafe { pop(stack) };
249 assert!(
250 !stack.is_null(),
251 "string_substring: need string, start, len"
252 );
253 let (stack, start_val) = unsafe { pop(stack) };
254 assert!(
255 !stack.is_null(),
256 "string_substring: need string, start, len"
257 );
258 let (stack, str_val) = unsafe { pop(stack) };
259
260 match (str_val, start_val, len_val) {
261 (Value::String(s), Value::Int(start), Value::Int(len)) => {
262 let result = if start < 0 || len < 0 {
263 String::new()
264 } else {
265 s.as_str()
266 .chars()
267 .skip(start as usize)
268 .take(len as usize)
269 .collect()
270 };
271 unsafe { push(stack, Value::String(global_string(result))) }
272 }
273 _ => panic!("string_substring: expected String, Int, Int on stack"),
274 }
275}
276
277#[unsafe(no_mangle)]
287pub unsafe extern "C" fn patch_seq_char_to_string(stack: Stack) -> Stack {
288 assert!(!stack.is_null(), "char_to_string: stack is empty");
289
290 let (stack, code_point_val) = unsafe { pop(stack) };
291
292 match code_point_val {
293 Value::Int(code_point) => {
294 let result = if !(0..=0x10FFFF).contains(&code_point) {
295 String::new()
297 } else {
298 match char::from_u32(code_point as u32) {
299 Some(c) => c.to_string(),
300 None => String::new(), }
302 };
303 unsafe { push(stack, Value::String(global_string(result))) }
304 }
305 _ => panic!("char_to_string: expected Int on stack"),
306 }
307}
308
309#[unsafe(no_mangle)]
319pub unsafe extern "C" fn patch_seq_string_find(stack: Stack) -> Stack {
320 assert!(!stack.is_null(), "string_find: stack is empty");
321
322 let (stack, needle_val) = unsafe { pop(stack) };
323 assert!(!stack.is_null(), "string_find: need string and needle");
324 let (stack, str_val) = unsafe { pop(stack) };
325
326 match (str_val, needle_val) {
327 (Value::String(haystack), Value::String(needle)) => {
328 let haystack_str = haystack.as_str();
329 let needle_str = needle.as_str();
330
331 let result = match haystack_str.find(needle_str) {
333 Some(byte_pos) => {
334 haystack_str[..byte_pos].chars().count() as i64
336 }
337 None => -1,
338 };
339 unsafe { push(stack, Value::Int(result)) }
340 }
341 _ => panic!("string_find: expected two Strings on stack"),
342 }
343}
344
345#[unsafe(no_mangle)]
352pub unsafe extern "C" fn patch_seq_string_trim(stack: Stack) -> Stack {
353 assert!(!stack.is_null(), "string_trim: stack is empty");
354
355 let (stack, str_val) = unsafe { pop(stack) };
356
357 match str_val {
358 Value::String(s) => {
359 let trimmed = s.as_str().trim();
360 unsafe { push(stack, Value::String(global_string(trimmed.to_owned()))) }
361 }
362 _ => panic!("string_trim: expected String on stack"),
363 }
364}
365
366#[unsafe(no_mangle)]
373pub unsafe extern "C" fn patch_seq_string_to_upper(stack: Stack) -> Stack {
374 assert!(!stack.is_null(), "string_to_upper: stack is empty");
375
376 let (stack, str_val) = unsafe { pop(stack) };
377
378 match str_val {
379 Value::String(s) => {
380 let upper = s.as_str().to_uppercase();
381 unsafe { push(stack, Value::String(global_string(upper))) }
382 }
383 _ => panic!("string_to_upper: expected String on stack"),
384 }
385}
386
387#[unsafe(no_mangle)]
394pub unsafe extern "C" fn patch_seq_string_to_lower(stack: Stack) -> Stack {
395 assert!(!stack.is_null(), "string_to_lower: stack is empty");
396
397 let (stack, str_val) = unsafe { pop(stack) };
398
399 match str_val {
400 Value::String(s) => {
401 let lower = s.as_str().to_lowercase();
402 unsafe { push(stack, Value::String(global_string(lower))) }
403 }
404 _ => panic!("string_to_lower: expected String on stack"),
405 }
406}
407
408#[unsafe(no_mangle)]
415pub unsafe extern "C" fn patch_seq_string_equal(stack: Stack) -> Stack {
416 assert!(!stack.is_null(), "string_equal: stack is empty");
417
418 let (stack, str2_val) = unsafe { pop(stack) };
419 assert!(!stack.is_null(), "string_equal: need two strings");
420 let (stack, str1_val) = unsafe { pop(stack) };
421
422 match (str1_val, str2_val) {
423 (Value::String(s1), Value::String(s2)) => {
424 let equal = s1.as_str() == s2.as_str();
425 unsafe { push(stack, Value::Bool(equal)) }
426 }
427 _ => panic!("string_equal: expected two strings on stack"),
428 }
429}
430
431#[unsafe(no_mangle)]
441pub unsafe extern "C" fn patch_seq_symbol_equal(stack: Stack) -> Stack {
442 assert!(!stack.is_null(), "symbol_equal: stack is empty");
443
444 let (stack, sym2_val) = unsafe { pop(stack) };
445 assert!(!stack.is_null(), "symbol_equal: need two symbols");
446 let (stack, sym1_val) = unsafe { pop(stack) };
447
448 match (sym1_val, sym2_val) {
449 (Value::Symbol(s1), Value::Symbol(s2)) => {
450 let equal = if s1.is_interned() && s2.is_interned() {
452 s1.as_ptr() == s2.as_ptr()
453 } else {
454 s1.as_str() == s2.as_str()
456 };
457 unsafe { push(stack, Value::Bool(equal)) }
458 }
459 _ => panic!("symbol_equal: expected two symbols on stack"),
460 }
461}
462
463#[unsafe(no_mangle)]
480pub unsafe extern "C" fn patch_seq_json_escape(stack: Stack) -> Stack {
481 assert!(!stack.is_null(), "json_escape: stack is empty");
482
483 let (stack, value) = unsafe { pop(stack) };
484
485 match value {
486 Value::String(s) => {
487 let input = s.as_str();
488 let mut result = String::with_capacity(input.len() + 16);
489
490 for ch in input.chars() {
491 match ch {
492 '"' => result.push_str("\\\""),
493 '\\' => result.push_str("\\\\"),
494 '\n' => result.push_str("\\n"),
495 '\r' => result.push_str("\\r"),
496 '\t' => result.push_str("\\t"),
497 '\x08' => result.push_str("\\b"), '\x0C' => result.push_str("\\f"), c if c.is_control() => {
502 result.push_str(&format!("\\u{:04X}", c as u32));
503 }
504 c => result.push(c),
505 }
506 }
507
508 unsafe { push(stack, Value::String(global_string(result))) }
509 }
510 _ => panic!("json_escape: expected String on stack"),
511 }
512}
513
514#[unsafe(no_mangle)]
527pub unsafe extern "C" fn patch_seq_string_to_int(stack: Stack) -> Stack {
528 if stack.is_null() {
529 set_runtime_error("string->int: stack is empty");
530 return stack;
531 }
532 let (stack, val) = unsafe { pop(stack) };
533
534 match val {
535 Value::String(s) => match s.as_str().trim().parse::<i64>() {
536 Ok(i) => {
537 let stack = unsafe { push(stack, Value::Int(i)) };
538 unsafe { push(stack, Value::Bool(true)) }
539 }
540 Err(_) => {
541 let stack = unsafe { push(stack, Value::Int(0)) };
542 unsafe { push(stack, Value::Bool(false)) }
543 }
544 },
545 _ => {
546 set_runtime_error("string->int: expected String on stack");
547 let stack = unsafe { push(stack, Value::Int(0)) };
548 unsafe { push(stack, Value::Bool(false)) }
549 }
550 }
551}
552
553#[unsafe(no_mangle)]
563pub unsafe extern "C" fn patch_seq_string_chomp(stack: Stack) -> Stack {
564 assert!(!stack.is_null(), "string_chomp: stack is empty");
565
566 let (stack, str_val) = unsafe { pop(stack) };
567
568 match str_val {
569 Value::String(s) => {
570 let mut result = s.as_str().to_owned();
571 if result.ends_with('\n') {
572 result.pop();
573 if result.ends_with('\r') {
574 result.pop();
575 }
576 }
577 unsafe { push(stack, Value::String(global_string(result))) }
578 }
579 _ => panic!("string_chomp: expected String on stack"),
580 }
581}
582
583pub use patch_seq_char_to_string as char_to_string;
585pub use patch_seq_json_escape as json_escape;
586pub use patch_seq_string_byte_length as string_byte_length;
587pub use patch_seq_string_char_at as string_char_at;
588pub use patch_seq_string_chomp as string_chomp;
589pub use patch_seq_string_concat as string_concat;
590pub use patch_seq_string_contains as string_contains;
591pub use patch_seq_string_empty as string_empty;
592pub use patch_seq_string_equal as string_equal;
593pub use patch_seq_string_find as string_find;
594pub use patch_seq_string_length as string_length;
595pub use patch_seq_string_split as string_split;
596pub use patch_seq_string_starts_with as string_starts_with;
597pub use patch_seq_string_substring as string_substring;
598pub use patch_seq_string_to_int as string_to_int;
599pub use patch_seq_string_to_lower as string_to_lower;
600pub use patch_seq_string_to_upper as string_to_upper;
601pub use patch_seq_string_trim as string_trim;
602
603#[cfg(not(feature = "nanbox"))]
631#[unsafe(no_mangle)]
632pub unsafe extern "C" fn patch_seq_string_to_cstring(stack: Stack, _out: *mut u8) -> *mut u8 {
633 assert!(!stack.is_null(), "string_to_cstring: stack is empty");
634
635 use crate::stack::{DISC_STRING, peek_sv};
636
637 let sv = unsafe { peek_sv(stack) };
639 if sv.slot0 != DISC_STRING {
640 panic!(
641 "string_to_cstring: expected String on stack, got discriminant {}",
642 sv.slot0
643 );
644 }
645
646 let str_ptr = sv.slot1 as *const u8;
648 let len = sv.slot2 as usize;
649
650 let alloc_size = len.checked_add(1).unwrap_or_else(|| {
652 panic!(
653 "string_to_cstring: string too large for C conversion (len={})",
654 len
655 )
656 });
657
658 let ptr = unsafe { libc::malloc(alloc_size) as *mut u8 };
660 if ptr.is_null() {
661 panic!("string_to_cstring: malloc failed");
662 }
663
664 unsafe {
666 std::ptr::copy_nonoverlapping(str_ptr, ptr, len);
667 *ptr.add(len) = 0;
669 }
670
671 ptr
672}
673
674#[cfg(feature = "nanbox")]
680#[unsafe(no_mangle)]
681pub unsafe extern "C" fn patch_seq_string_to_cstring(stack: Stack, _out: *mut u8) -> *mut u8 {
682 assert!(!stack.is_null(), "string_to_cstring: stack is empty");
683
684 use crate::stack::peek_sv;
685
686 let sv = unsafe { peek_sv(stack) };
688 if !sv.0.is_string() {
689 panic!("string_to_cstring: expected String on stack");
690 }
691
692 let seq_str_ptr = unsafe { sv.0.as_string_ptr() };
694 let seq_str = unsafe { &*seq_str_ptr };
695 let str_ptr = seq_str.as_ptr();
696 let len = seq_str.len();
697
698 let alloc_size = len.checked_add(1).unwrap_or_else(|| {
700 panic!(
701 "string_to_cstring: string too large for C conversion (len={})",
702 len
703 )
704 });
705
706 let ptr = unsafe { libc::malloc(alloc_size) as *mut u8 };
708 if ptr.is_null() {
709 panic!("string_to_cstring: malloc failed");
710 }
711
712 unsafe {
714 std::ptr::copy_nonoverlapping(str_ptr, ptr, len);
715 *ptr.add(len) = 0;
717 }
718
719 ptr
720}
721
722#[unsafe(no_mangle)]
731pub unsafe extern "C" fn patch_seq_cstring_to_string(stack: Stack, cstr: *const u8) -> Stack {
732 if cstr.is_null() {
733 return unsafe { push(stack, Value::String(global_string(String::new()))) };
735 }
736
737 let len = unsafe { libc::strlen(cstr as *const libc::c_char) };
739
740 let slice = unsafe { std::slice::from_raw_parts(cstr, len) };
742 let s = String::from_utf8_lossy(slice).into_owned();
743
744 unsafe { push(stack, Value::String(global_string(s))) }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750
751 #[test]
752 fn test_string_split_simple() {
753 unsafe {
754 let stack = crate::stack::alloc_test_stack();
755 let stack = push(stack, Value::String(global_string("a b c".to_owned())));
756 let stack = push(stack, Value::String(global_string(" ".to_owned())));
757
758 let stack = string_split(stack);
759
760 let (_stack, result) = pop(stack);
762 match result {
763 Value::Variant(v) => {
764 assert_eq!(v.tag.as_str(), "List");
765 assert_eq!(v.fields.len(), 3);
766 assert_eq!(v.fields[0], Value::String(global_string("a".to_owned())));
767 assert_eq!(v.fields[1], Value::String(global_string("b".to_owned())));
768 assert_eq!(v.fields[2], Value::String(global_string("c".to_owned())));
769 }
770 _ => panic!("Expected Variant, got {:?}", result),
771 }
772 }
773 }
774
775 #[test]
776 fn test_string_split_empty() {
777 unsafe {
778 let stack = crate::stack::alloc_test_stack();
779 let stack = push(stack, Value::String(global_string("".to_owned())));
780 let stack = push(stack, Value::String(global_string(" ".to_owned())));
781
782 let stack = string_split(stack);
783
784 let (_stack, result) = pop(stack);
786 match result {
787 Value::Variant(v) => {
788 assert_eq!(v.tag.as_str(), "List");
789 assert_eq!(v.fields.len(), 1);
790 assert_eq!(v.fields[0], Value::String(global_string("".to_owned())));
791 }
792 _ => panic!("Expected Variant, got {:?}", result),
793 }
794 }
795 }
796
797 #[test]
798 fn test_string_empty_true() {
799 unsafe {
800 let stack = crate::stack::alloc_test_stack();
801 let stack = push(stack, Value::String(global_string("".to_owned())));
802
803 let stack = string_empty(stack);
804
805 let (_stack, result) = pop(stack);
806 assert_eq!(result, Value::Bool(true));
807 }
808 }
809
810 #[test]
811 fn test_string_empty_false() {
812 unsafe {
813 let stack = crate::stack::alloc_test_stack();
814 let stack = push(stack, Value::String(global_string("hello".to_owned())));
815
816 let stack = string_empty(stack);
817
818 let (_stack, result) = pop(stack);
819 assert_eq!(result, Value::Bool(false));
820 }
821 }
822
823 #[test]
824 fn test_string_contains_true() {
825 unsafe {
826 let stack = crate::stack::alloc_test_stack();
827 let stack = push(
828 stack,
829 Value::String(global_string("hello world".to_owned())),
830 );
831 let stack = push(stack, Value::String(global_string("world".to_owned())));
832
833 let stack = string_contains(stack);
834
835 let (_stack, result) = pop(stack);
836 assert_eq!(result, Value::Bool(true));
837 }
838 }
839
840 #[test]
841 fn test_string_contains_false() {
842 unsafe {
843 let stack = crate::stack::alloc_test_stack();
844 let stack = push(
845 stack,
846 Value::String(global_string("hello world".to_owned())),
847 );
848 let stack = push(stack, Value::String(global_string("foo".to_owned())));
849
850 let stack = string_contains(stack);
851
852 let (_stack, result) = pop(stack);
853 assert_eq!(result, Value::Bool(false));
854 }
855 }
856
857 #[test]
858 fn test_string_starts_with_true() {
859 unsafe {
860 let stack = crate::stack::alloc_test_stack();
861 let stack = push(
862 stack,
863 Value::String(global_string("hello world".to_owned())),
864 );
865 let stack = push(stack, Value::String(global_string("hello".to_owned())));
866
867 let stack = string_starts_with(stack);
868
869 let (_stack, result) = pop(stack);
870 assert_eq!(result, Value::Bool(true));
871 }
872 }
873
874 #[test]
875 fn test_string_starts_with_false() {
876 unsafe {
877 let stack = crate::stack::alloc_test_stack();
878 let stack = push(
879 stack,
880 Value::String(global_string("hello world".to_owned())),
881 );
882 let stack = push(stack, Value::String(global_string("world".to_owned())));
883
884 let stack = string_starts_with(stack);
885
886 let (_stack, result) = pop(stack);
887 assert_eq!(result, Value::Bool(false));
888 }
889 }
890
891 #[test]
892 fn test_http_request_line_parsing() {
893 unsafe {
895 let stack = crate::stack::alloc_test_stack();
896 let stack = push(
897 stack,
898 Value::String(global_string("GET /api/users HTTP/1.1".to_owned())),
899 );
900 let stack = push(stack, Value::String(global_string(" ".to_owned())));
901
902 let stack = string_split(stack);
903
904 let (_stack, result) = pop(stack);
906 match result {
907 Value::Variant(v) => {
908 assert_eq!(v.tag.as_str(), "List");
909 assert_eq!(v.fields.len(), 3);
910 assert_eq!(v.fields[0], Value::String(global_string("GET".to_owned())));
911 assert_eq!(
912 v.fields[1],
913 Value::String(global_string("/api/users".to_owned()))
914 );
915 assert_eq!(
916 v.fields[2],
917 Value::String(global_string("HTTP/1.1".to_owned()))
918 );
919 }
920 _ => panic!("Expected Variant, got {:?}", result),
921 }
922 }
923 }
924
925 #[test]
926 fn test_path_routing() {
927 unsafe {
929 let stack = crate::stack::alloc_test_stack();
930 let stack = push(stack, Value::String(global_string("/api/users".to_owned())));
931 let stack = push(stack, Value::String(global_string("/api/".to_owned())));
932
933 let stack = string_starts_with(stack);
934
935 let (_stack, result) = pop(stack);
936 assert_eq!(result, Value::Bool(true));
937 }
938 }
939
940 #[test]
941 fn test_string_concat() {
942 unsafe {
943 let stack = crate::stack::alloc_test_stack();
944 let stack = push(stack, Value::String(global_string("Hello, ".to_owned())));
945 let stack = push(stack, Value::String(global_string("World!".to_owned())));
946
947 let stack = string_concat(stack);
948
949 let (_stack, result) = pop(stack);
950 assert_eq!(
951 result,
952 Value::String(global_string("Hello, World!".to_owned()))
953 );
954 }
955 }
956
957 #[test]
958 fn test_string_length() {
959 unsafe {
960 let stack = crate::stack::alloc_test_stack();
961 let stack = push(stack, Value::String(global_string("Hello".to_owned())));
962
963 let stack = string_length(stack);
964
965 let (_stack, result) = pop(stack);
966 assert_eq!(result, Value::Int(5));
967 }
968 }
969
970 #[test]
971 fn test_string_length_empty() {
972 unsafe {
973 let stack = crate::stack::alloc_test_stack();
974 let stack = push(stack, Value::String(global_string("".to_owned())));
975
976 let stack = string_length(stack);
977
978 let (_stack, result) = pop(stack);
979 assert_eq!(result, Value::Int(0));
980 }
981 }
982
983 #[test]
984 fn test_string_trim() {
985 unsafe {
986 let stack = crate::stack::alloc_test_stack();
987 let stack = push(
988 stack,
989 Value::String(global_string(" Hello, World! ".to_owned())),
990 );
991
992 let stack = string_trim(stack);
993
994 let (_stack, result) = pop(stack);
995 assert_eq!(
996 result,
997 Value::String(global_string("Hello, World!".to_owned()))
998 );
999 }
1000 }
1001
1002 #[test]
1003 fn test_string_to_upper() {
1004 unsafe {
1005 let stack = crate::stack::alloc_test_stack();
1006 let stack = push(
1007 stack,
1008 Value::String(global_string("Hello, World!".to_owned())),
1009 );
1010
1011 let stack = string_to_upper(stack);
1012
1013 let (_stack, result) = pop(stack);
1014 assert_eq!(
1015 result,
1016 Value::String(global_string("HELLO, WORLD!".to_owned()))
1017 );
1018 }
1019 }
1020
1021 #[test]
1022 fn test_string_to_lower() {
1023 unsafe {
1024 let stack = crate::stack::alloc_test_stack();
1025 let stack = push(
1026 stack,
1027 Value::String(global_string("Hello, World!".to_owned())),
1028 );
1029
1030 let stack = string_to_lower(stack);
1031
1032 let (_stack, result) = pop(stack);
1033 assert_eq!(
1034 result,
1035 Value::String(global_string("hello, world!".to_owned()))
1036 );
1037 }
1038 }
1039
1040 #[test]
1041 fn test_http_header_content_length() {
1042 unsafe {
1044 let stack = crate::stack::alloc_test_stack();
1045 let stack = push(
1046 stack,
1047 Value::String(global_string("Content-Length: ".to_owned())),
1048 );
1049 let stack = push(stack, Value::String(global_string("42".to_owned())));
1050
1051 let stack = string_concat(stack);
1052
1053 let (_stack, result) = pop(stack);
1054 assert_eq!(
1055 result,
1056 Value::String(global_string("Content-Length: 42".to_owned()))
1057 );
1058 }
1059 }
1060
1061 #[test]
1062 fn test_string_equal_true() {
1063 unsafe {
1064 let stack = crate::stack::alloc_test_stack();
1065 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1066 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1067
1068 let stack = string_equal(stack);
1069
1070 let (_stack, result) = pop(stack);
1071 assert_eq!(result, Value::Bool(true));
1072 }
1073 }
1074
1075 #[test]
1076 fn test_string_equal_false() {
1077 unsafe {
1078 let stack = crate::stack::alloc_test_stack();
1079 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1080 let stack = push(stack, Value::String(global_string("world".to_owned())));
1081
1082 let stack = string_equal(stack);
1083
1084 let (_stack, result) = pop(stack);
1085 assert_eq!(result, Value::Bool(false));
1086 }
1087 }
1088
1089 #[test]
1090 fn test_string_equal_empty_strings() {
1091 unsafe {
1092 let stack = crate::stack::alloc_test_stack();
1093 let stack = push(stack, Value::String(global_string("".to_owned())));
1094 let stack = push(stack, Value::String(global_string("".to_owned())));
1095
1096 let stack = string_equal(stack);
1097
1098 let (_stack, result) = pop(stack);
1099 assert_eq!(result, Value::Bool(true));
1100 }
1101 }
1102
1103 #[test]
1106 fn test_string_length_utf8() {
1107 unsafe {
1109 let stack = crate::stack::alloc_test_stack();
1110 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1111
1112 let stack = string_length(stack);
1113
1114 let (_stack, result) = pop(stack);
1115 assert_eq!(result, Value::Int(5)); }
1117 }
1118
1119 #[test]
1120 fn test_string_length_emoji() {
1121 unsafe {
1123 let stack = crate::stack::alloc_test_stack();
1124 let stack = push(stack, Value::String(global_string("hi🎉".to_owned())));
1125
1126 let stack = string_length(stack);
1127
1128 let (_stack, result) = pop(stack);
1129 assert_eq!(result, Value::Int(3)); }
1131 }
1132
1133 #[test]
1134 fn test_string_byte_length_ascii() {
1135 unsafe {
1136 let stack = crate::stack::alloc_test_stack();
1137 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1138
1139 let stack = string_byte_length(stack);
1140
1141 let (_stack, result) = pop(stack);
1142 assert_eq!(result, Value::Int(5)); }
1144 }
1145
1146 #[test]
1147 fn test_string_byte_length_utf8() {
1148 unsafe {
1150 let stack = crate::stack::alloc_test_stack();
1151 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1152
1153 let stack = string_byte_length(stack);
1154
1155 let (_stack, result) = pop(stack);
1156 assert_eq!(result, Value::Int(6)); }
1158 }
1159
1160 #[test]
1161 fn test_string_char_at_ascii() {
1162 unsafe {
1163 let stack = crate::stack::alloc_test_stack();
1164 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1165 let stack = push(stack, Value::Int(0));
1166
1167 let stack = string_char_at(stack);
1168
1169 let (_stack, result) = pop(stack);
1170 assert_eq!(result, Value::Int(104)); }
1172 }
1173
1174 #[test]
1175 fn test_string_char_at_utf8() {
1176 unsafe {
1178 let stack = crate::stack::alloc_test_stack();
1179 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1180 let stack = push(stack, Value::Int(1));
1181
1182 let stack = string_char_at(stack);
1183
1184 let (_stack, result) = pop(stack);
1185 assert_eq!(result, Value::Int(233)); }
1187 }
1188
1189 #[test]
1190 fn test_string_char_at_out_of_bounds() {
1191 unsafe {
1192 let stack = crate::stack::alloc_test_stack();
1193 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1194 let stack = push(stack, Value::Int(10)); let stack = string_char_at(stack);
1197
1198 let (_stack, result) = pop(stack);
1199 assert_eq!(result, Value::Int(-1));
1200 }
1201 }
1202
1203 #[test]
1204 fn test_string_char_at_negative() {
1205 unsafe {
1206 let stack = crate::stack::alloc_test_stack();
1207 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1208 let stack = push(stack, Value::Int(-1));
1209
1210 let stack = string_char_at(stack);
1211
1212 let (_stack, result) = pop(stack);
1213 assert_eq!(result, Value::Int(-1));
1214 }
1215 }
1216
1217 #[test]
1218 fn test_string_substring_simple() {
1219 unsafe {
1220 let stack = crate::stack::alloc_test_stack();
1221 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1222 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1226
1227 let (_stack, result) = pop(stack);
1228 assert_eq!(result, Value::String(global_string("ell".to_owned())));
1229 }
1230 }
1231
1232 #[test]
1233 fn test_string_substring_utf8() {
1234 unsafe {
1236 let stack = crate::stack::alloc_test_stack();
1237 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1238 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1242
1243 let (_stack, result) = pop(stack);
1244 assert_eq!(result, Value::String(global_string("éll".to_owned())));
1245 }
1246 }
1247
1248 #[test]
1249 fn test_string_substring_clamp() {
1250 unsafe {
1252 let stack = crate::stack::alloc_test_stack();
1253 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1254 let stack = push(stack, Value::Int(2)); let stack = push(stack, Value::Int(100)); let stack = string_substring(stack);
1258
1259 let (_stack, result) = pop(stack);
1260 assert_eq!(result, Value::String(global_string("llo".to_owned())));
1261 }
1262 }
1263
1264 #[test]
1265 fn test_string_substring_beyond_end() {
1266 unsafe {
1268 let stack = crate::stack::alloc_test_stack();
1269 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1270 let stack = push(stack, Value::Int(10)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1274
1275 let (_stack, result) = pop(stack);
1276 assert_eq!(result, Value::String(global_string("".to_owned())));
1277 }
1278 }
1279
1280 #[test]
1281 fn test_char_to_string_ascii() {
1282 unsafe {
1283 let stack = crate::stack::alloc_test_stack();
1284 let stack = push(stack, Value::Int(65)); let stack = char_to_string(stack);
1287
1288 let (_stack, result) = pop(stack);
1289 assert_eq!(result, Value::String(global_string("A".to_owned())));
1290 }
1291 }
1292
1293 #[test]
1294 fn test_char_to_string_utf8() {
1295 unsafe {
1296 let stack = crate::stack::alloc_test_stack();
1297 let stack = push(stack, Value::Int(233)); let stack = char_to_string(stack);
1300
1301 let (_stack, result) = pop(stack);
1302 assert_eq!(result, Value::String(global_string("é".to_owned())));
1303 }
1304 }
1305
1306 #[test]
1307 fn test_char_to_string_newline() {
1308 unsafe {
1309 let stack = crate::stack::alloc_test_stack();
1310 let stack = push(stack, Value::Int(10)); let stack = char_to_string(stack);
1313
1314 let (_stack, result) = pop(stack);
1315 assert_eq!(result, Value::String(global_string("\n".to_owned())));
1316 }
1317 }
1318
1319 #[test]
1320 fn test_char_to_string_invalid() {
1321 unsafe {
1322 let stack = crate::stack::alloc_test_stack();
1323 let stack = push(stack, Value::Int(-1)); let stack = char_to_string(stack);
1326
1327 let (_stack, result) = pop(stack);
1328 assert_eq!(result, Value::String(global_string("".to_owned())));
1329 }
1330 }
1331
1332 #[test]
1333 fn test_string_find_found() {
1334 unsafe {
1335 let stack = crate::stack::alloc_test_stack();
1336 let stack = push(
1337 stack,
1338 Value::String(global_string("hello world".to_owned())),
1339 );
1340 let stack = push(stack, Value::String(global_string("world".to_owned())));
1341
1342 let stack = string_find(stack);
1343
1344 let (_stack, result) = pop(stack);
1345 assert_eq!(result, Value::Int(6)); }
1347 }
1348
1349 #[test]
1350 fn test_string_find_not_found() {
1351 unsafe {
1352 let stack = crate::stack::alloc_test_stack();
1353 let stack = push(
1354 stack,
1355 Value::String(global_string("hello world".to_owned())),
1356 );
1357 let stack = push(stack, Value::String(global_string("xyz".to_owned())));
1358
1359 let stack = string_find(stack);
1360
1361 let (_stack, result) = pop(stack);
1362 assert_eq!(result, Value::Int(-1));
1363 }
1364 }
1365
1366 #[test]
1367 fn test_string_find_first_match() {
1368 unsafe {
1370 let stack = crate::stack::alloc_test_stack();
1371 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1372 let stack = push(stack, Value::String(global_string("l".to_owned())));
1373
1374 let stack = string_find(stack);
1375
1376 let (_stack, result) = pop(stack);
1377 assert_eq!(result, Value::Int(2)); }
1379 }
1380
1381 #[test]
1382 fn test_string_find_utf8() {
1383 unsafe {
1385 let stack = crate::stack::alloc_test_stack();
1386 let stack = push(
1387 stack,
1388 Value::String(global_string("héllo wörld".to_owned())),
1389 );
1390 let stack = push(stack, Value::String(global_string("wörld".to_owned())));
1391
1392 let stack = string_find(stack);
1393
1394 let (_stack, result) = pop(stack);
1395 assert_eq!(result, Value::Int(6)); }
1397 }
1398
1399 #[test]
1402 fn test_json_escape_quotes() {
1403 unsafe {
1404 let stack = crate::stack::alloc_test_stack();
1405 let stack = push(
1406 stack,
1407 Value::String(global_string("hello \"world\"".to_owned())),
1408 );
1409
1410 let stack = json_escape(stack);
1411
1412 let (_stack, result) = pop(stack);
1413 assert_eq!(
1414 result,
1415 Value::String(global_string("hello \\\"world\\\"".to_owned()))
1416 );
1417 }
1418 }
1419
1420 #[test]
1421 fn test_json_escape_backslash() {
1422 unsafe {
1423 let stack = crate::stack::alloc_test_stack();
1424 let stack = push(
1425 stack,
1426 Value::String(global_string("path\\to\\file".to_owned())),
1427 );
1428
1429 let stack = json_escape(stack);
1430
1431 let (_stack, result) = pop(stack);
1432 assert_eq!(
1433 result,
1434 Value::String(global_string("path\\\\to\\\\file".to_owned()))
1435 );
1436 }
1437 }
1438
1439 #[test]
1440 fn test_json_escape_newline_tab() {
1441 unsafe {
1442 let stack = crate::stack::alloc_test_stack();
1443 let stack = push(
1444 stack,
1445 Value::String(global_string("line1\nline2\ttabbed".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("line1\\nline2\\ttabbed".to_owned()))
1454 );
1455 }
1456 }
1457
1458 #[test]
1459 fn test_json_escape_carriage_return() {
1460 unsafe {
1461 let stack = crate::stack::alloc_test_stack();
1462 let stack = push(
1463 stack,
1464 Value::String(global_string("line1\r\nline2".to_owned())),
1465 );
1466
1467 let stack = json_escape(stack);
1468
1469 let (_stack, result) = pop(stack);
1470 assert_eq!(
1471 result,
1472 Value::String(global_string("line1\\r\\nline2".to_owned()))
1473 );
1474 }
1475 }
1476
1477 #[test]
1478 fn test_json_escape_control_chars() {
1479 unsafe {
1480 let stack = crate::stack::alloc_test_stack();
1481 let stack = push(
1483 stack,
1484 Value::String(global_string("a\x08b\x0Cc".to_owned())),
1485 );
1486
1487 let stack = json_escape(stack);
1488
1489 let (_stack, result) = pop(stack);
1490 assert_eq!(result, Value::String(global_string("a\\bb\\fc".to_owned())));
1491 }
1492 }
1493
1494 #[test]
1495 fn test_json_escape_unicode_control() {
1496 unsafe {
1497 let stack = crate::stack::alloc_test_stack();
1498 let stack = push(stack, Value::String(global_string("a\x00b".to_owned())));
1500
1501 let stack = json_escape(stack);
1502
1503 let (_stack, result) = pop(stack);
1504 assert_eq!(result, Value::String(global_string("a\\u0000b".to_owned())));
1505 }
1506 }
1507
1508 #[test]
1509 fn test_json_escape_mixed_special_chars() {
1510 unsafe {
1512 let stack = crate::stack::alloc_test_stack();
1513 let stack = push(
1514 stack,
1515 Value::String(global_string("Line 1\nLine \"2\"\ttab\r\n".to_owned())),
1516 );
1517
1518 let stack = json_escape(stack);
1519
1520 let (_stack, result) = pop(stack);
1521 assert_eq!(
1522 result,
1523 Value::String(global_string(
1524 "Line 1\\nLine \\\"2\\\"\\ttab\\r\\n".to_owned()
1525 ))
1526 );
1527 }
1528 }
1529
1530 #[test]
1531 fn test_json_escape_no_change() {
1532 unsafe {
1534 let stack = crate::stack::alloc_test_stack();
1535 let stack = push(
1536 stack,
1537 Value::String(global_string("Hello, World!".to_owned())),
1538 );
1539
1540 let stack = json_escape(stack);
1541
1542 let (_stack, result) = pop(stack);
1543 assert_eq!(
1544 result,
1545 Value::String(global_string("Hello, World!".to_owned()))
1546 );
1547 }
1548 }
1549
1550 #[test]
1551 fn test_json_escape_empty_string() {
1552 unsafe {
1553 let stack = crate::stack::alloc_test_stack();
1554 let stack = push(stack, Value::String(global_string("".to_owned())));
1555
1556 let stack = json_escape(stack);
1557
1558 let (_stack, result) = pop(stack);
1559 assert_eq!(result, Value::String(global_string("".to_owned())));
1560 }
1561 }
1562
1563 #[test]
1566 fn test_string_to_int_success() {
1567 unsafe {
1568 let stack = crate::stack::alloc_test_stack();
1569 let stack = push(stack, Value::String(global_string("42".to_owned())));
1570
1571 let stack = string_to_int(stack);
1572
1573 let (stack, success) = pop(stack);
1574 let (_stack, value) = pop(stack);
1575 assert_eq!(success, Value::Bool(true));
1576 assert_eq!(value, Value::Int(42));
1577 }
1578 }
1579
1580 #[test]
1581 fn test_string_to_int_negative() {
1582 unsafe {
1583 let stack = crate::stack::alloc_test_stack();
1584 let stack = push(stack, Value::String(global_string("-99".to_owned())));
1585
1586 let stack = string_to_int(stack);
1587
1588 let (stack, success) = pop(stack);
1589 let (_stack, value) = pop(stack);
1590 assert_eq!(success, Value::Bool(true));
1591 assert_eq!(value, Value::Int(-99));
1592 }
1593 }
1594
1595 #[test]
1596 fn test_string_to_int_with_whitespace() {
1597 unsafe {
1598 let stack = crate::stack::alloc_test_stack();
1599 let stack = push(stack, Value::String(global_string(" 123 ".to_owned())));
1600
1601 let stack = string_to_int(stack);
1602
1603 let (stack, success) = pop(stack);
1604 let (_stack, value) = pop(stack);
1605 assert_eq!(success, Value::Bool(true));
1606 assert_eq!(value, Value::Int(123));
1607 }
1608 }
1609
1610 #[test]
1611 fn test_string_to_int_failure() {
1612 unsafe {
1613 let stack = crate::stack::alloc_test_stack();
1614 let stack = push(
1615 stack,
1616 Value::String(global_string("not a number".to_owned())),
1617 );
1618
1619 let stack = string_to_int(stack);
1620
1621 let (stack, success) = pop(stack);
1622 let (_stack, value) = pop(stack);
1623 assert_eq!(success, Value::Bool(false));
1624 assert_eq!(value, Value::Int(0));
1625 }
1626 }
1627
1628 #[test]
1629 fn test_string_to_int_empty() {
1630 unsafe {
1631 let stack = crate::stack::alloc_test_stack();
1632 let stack = push(stack, Value::String(global_string("".to_owned())));
1633
1634 let stack = string_to_int(stack);
1635
1636 let (stack, success) = pop(stack);
1637 let (_stack, value) = pop(stack);
1638 assert_eq!(success, Value::Bool(false));
1639 assert_eq!(value, Value::Int(0));
1640 }
1641 }
1642
1643 #[test]
1644 fn test_string_to_int_leading_zeros() {
1645 unsafe {
1646 let stack = crate::stack::alloc_test_stack();
1647 let stack = push(stack, Value::String(global_string("007".to_owned())));
1648
1649 let stack = string_to_int(stack);
1650
1651 let (stack, success) = pop(stack);
1652 let (_stack, value) = pop(stack);
1653 assert_eq!(success, Value::Bool(true));
1654 assert_eq!(value, Value::Int(7));
1655 }
1656 }
1657
1658 #[test]
1659 fn test_string_to_int_type_error() {
1660 unsafe {
1661 crate::error::clear_runtime_error();
1662
1663 let stack = crate::stack::alloc_test_stack();
1664 let stack = push(stack, Value::Int(42)); let stack = string_to_int(stack);
1667
1668 assert!(crate::error::has_runtime_error());
1670 let error = crate::error::take_runtime_error().unwrap();
1671 assert!(error.contains("expected String"));
1672
1673 let (stack, success) = pop(stack);
1675 assert_eq!(success, Value::Bool(false));
1676 let (_stack, value) = pop(stack);
1677 assert_eq!(value, Value::Int(0));
1678 }
1679 }
1680}