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#[unsafe(no_mangle)]
631pub unsafe extern "C" fn patch_seq_string_to_cstring(stack: Stack, _out: *mut u8) -> *mut u8 {
632 assert!(!stack.is_null(), "string_to_cstring: stack is empty");
633
634 use crate::stack::{DISC_STRING, peek_sv};
635
636 let sv = unsafe { peek_sv(stack) };
638 if sv.slot0 != DISC_STRING {
639 panic!(
640 "string_to_cstring: expected String on stack, got discriminant {}",
641 sv.slot0
642 );
643 }
644
645 let str_ptr = sv.slot1 as *const u8;
647 let len = sv.slot2 as usize;
648
649 let alloc_size = len.checked_add(1).unwrap_or_else(|| {
651 panic!(
652 "string_to_cstring: string too large for C conversion (len={})",
653 len
654 )
655 });
656
657 let ptr = unsafe { libc::malloc(alloc_size) as *mut u8 };
659 if ptr.is_null() {
660 panic!("string_to_cstring: malloc failed");
661 }
662
663 unsafe {
665 std::ptr::copy_nonoverlapping(str_ptr, ptr, len);
666 *ptr.add(len) = 0;
668 }
669
670 ptr
671}
672
673#[unsafe(no_mangle)]
682pub unsafe extern "C" fn patch_seq_cstring_to_string(stack: Stack, cstr: *const u8) -> Stack {
683 if cstr.is_null() {
684 return unsafe { push(stack, Value::String(global_string(String::new()))) };
686 }
687
688 let len = unsafe { libc::strlen(cstr as *const libc::c_char) };
690
691 let slice = unsafe { std::slice::from_raw_parts(cstr, len) };
693 let s = String::from_utf8_lossy(slice).into_owned();
694
695 unsafe { push(stack, Value::String(global_string(s))) }
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 #[test]
703 fn test_string_split_simple() {
704 unsafe {
705 let stack = crate::stack::alloc_test_stack();
706 let stack = push(stack, Value::String(global_string("a b c".to_owned())));
707 let stack = push(stack, Value::String(global_string(" ".to_owned())));
708
709 let stack = string_split(stack);
710
711 let (_stack, result) = pop(stack);
713 match result {
714 Value::Variant(v) => {
715 assert_eq!(v.tag.as_str(), "List");
716 assert_eq!(v.fields.len(), 3);
717 assert_eq!(v.fields[0], Value::String(global_string("a".to_owned())));
718 assert_eq!(v.fields[1], Value::String(global_string("b".to_owned())));
719 assert_eq!(v.fields[2], Value::String(global_string("c".to_owned())));
720 }
721 _ => panic!("Expected Variant, got {:?}", result),
722 }
723 }
724 }
725
726 #[test]
727 fn test_string_split_empty() {
728 unsafe {
729 let stack = crate::stack::alloc_test_stack();
730 let stack = push(stack, Value::String(global_string("".to_owned())));
731 let stack = push(stack, Value::String(global_string(" ".to_owned())));
732
733 let stack = string_split(stack);
734
735 let (_stack, result) = pop(stack);
737 match result {
738 Value::Variant(v) => {
739 assert_eq!(v.tag.as_str(), "List");
740 assert_eq!(v.fields.len(), 1);
741 assert_eq!(v.fields[0], Value::String(global_string("".to_owned())));
742 }
743 _ => panic!("Expected Variant, got {:?}", result),
744 }
745 }
746 }
747
748 #[test]
749 fn test_string_empty_true() {
750 unsafe {
751 let stack = crate::stack::alloc_test_stack();
752 let stack = push(stack, Value::String(global_string("".to_owned())));
753
754 let stack = string_empty(stack);
755
756 let (_stack, result) = pop(stack);
757 assert_eq!(result, Value::Bool(true));
758 }
759 }
760
761 #[test]
762 fn test_string_empty_false() {
763 unsafe {
764 let stack = crate::stack::alloc_test_stack();
765 let stack = push(stack, Value::String(global_string("hello".to_owned())));
766
767 let stack = string_empty(stack);
768
769 let (_stack, result) = pop(stack);
770 assert_eq!(result, Value::Bool(false));
771 }
772 }
773
774 #[test]
775 fn test_string_contains_true() {
776 unsafe {
777 let stack = crate::stack::alloc_test_stack();
778 let stack = push(
779 stack,
780 Value::String(global_string("hello world".to_owned())),
781 );
782 let stack = push(stack, Value::String(global_string("world".to_owned())));
783
784 let stack = string_contains(stack);
785
786 let (_stack, result) = pop(stack);
787 assert_eq!(result, Value::Bool(true));
788 }
789 }
790
791 #[test]
792 fn test_string_contains_false() {
793 unsafe {
794 let stack = crate::stack::alloc_test_stack();
795 let stack = push(
796 stack,
797 Value::String(global_string("hello world".to_owned())),
798 );
799 let stack = push(stack, Value::String(global_string("foo".to_owned())));
800
801 let stack = string_contains(stack);
802
803 let (_stack, result) = pop(stack);
804 assert_eq!(result, Value::Bool(false));
805 }
806 }
807
808 #[test]
809 fn test_string_starts_with_true() {
810 unsafe {
811 let stack = crate::stack::alloc_test_stack();
812 let stack = push(
813 stack,
814 Value::String(global_string("hello world".to_owned())),
815 );
816 let stack = push(stack, Value::String(global_string("hello".to_owned())));
817
818 let stack = string_starts_with(stack);
819
820 let (_stack, result) = pop(stack);
821 assert_eq!(result, Value::Bool(true));
822 }
823 }
824
825 #[test]
826 fn test_string_starts_with_false() {
827 unsafe {
828 let stack = crate::stack::alloc_test_stack();
829 let stack = push(
830 stack,
831 Value::String(global_string("hello world".to_owned())),
832 );
833 let stack = push(stack, Value::String(global_string("world".to_owned())));
834
835 let stack = string_starts_with(stack);
836
837 let (_stack, result) = pop(stack);
838 assert_eq!(result, Value::Bool(false));
839 }
840 }
841
842 #[test]
843 fn test_http_request_line_parsing() {
844 unsafe {
846 let stack = crate::stack::alloc_test_stack();
847 let stack = push(
848 stack,
849 Value::String(global_string("GET /api/users HTTP/1.1".to_owned())),
850 );
851 let stack = push(stack, Value::String(global_string(" ".to_owned())));
852
853 let stack = string_split(stack);
854
855 let (_stack, result) = pop(stack);
857 match result {
858 Value::Variant(v) => {
859 assert_eq!(v.tag.as_str(), "List");
860 assert_eq!(v.fields.len(), 3);
861 assert_eq!(v.fields[0], Value::String(global_string("GET".to_owned())));
862 assert_eq!(
863 v.fields[1],
864 Value::String(global_string("/api/users".to_owned()))
865 );
866 assert_eq!(
867 v.fields[2],
868 Value::String(global_string("HTTP/1.1".to_owned()))
869 );
870 }
871 _ => panic!("Expected Variant, got {:?}", result),
872 }
873 }
874 }
875
876 #[test]
877 fn test_path_routing() {
878 unsafe {
880 let stack = crate::stack::alloc_test_stack();
881 let stack = push(stack, Value::String(global_string("/api/users".to_owned())));
882 let stack = push(stack, Value::String(global_string("/api/".to_owned())));
883
884 let stack = string_starts_with(stack);
885
886 let (_stack, result) = pop(stack);
887 assert_eq!(result, Value::Bool(true));
888 }
889 }
890
891 #[test]
892 fn test_string_concat() {
893 unsafe {
894 let stack = crate::stack::alloc_test_stack();
895 let stack = push(stack, Value::String(global_string("Hello, ".to_owned())));
896 let stack = push(stack, Value::String(global_string("World!".to_owned())));
897
898 let stack = string_concat(stack);
899
900 let (_stack, result) = pop(stack);
901 assert_eq!(
902 result,
903 Value::String(global_string("Hello, World!".to_owned()))
904 );
905 }
906 }
907
908 #[test]
909 fn test_string_length() {
910 unsafe {
911 let stack = crate::stack::alloc_test_stack();
912 let stack = push(stack, Value::String(global_string("Hello".to_owned())));
913
914 let stack = string_length(stack);
915
916 let (_stack, result) = pop(stack);
917 assert_eq!(result, Value::Int(5));
918 }
919 }
920
921 #[test]
922 fn test_string_length_empty() {
923 unsafe {
924 let stack = crate::stack::alloc_test_stack();
925 let stack = push(stack, Value::String(global_string("".to_owned())));
926
927 let stack = string_length(stack);
928
929 let (_stack, result) = pop(stack);
930 assert_eq!(result, Value::Int(0));
931 }
932 }
933
934 #[test]
935 fn test_string_trim() {
936 unsafe {
937 let stack = crate::stack::alloc_test_stack();
938 let stack = push(
939 stack,
940 Value::String(global_string(" Hello, World! ".to_owned())),
941 );
942
943 let stack = string_trim(stack);
944
945 let (_stack, result) = pop(stack);
946 assert_eq!(
947 result,
948 Value::String(global_string("Hello, World!".to_owned()))
949 );
950 }
951 }
952
953 #[test]
954 fn test_string_to_upper() {
955 unsafe {
956 let stack = crate::stack::alloc_test_stack();
957 let stack = push(
958 stack,
959 Value::String(global_string("Hello, World!".to_owned())),
960 );
961
962 let stack = string_to_upper(stack);
963
964 let (_stack, result) = pop(stack);
965 assert_eq!(
966 result,
967 Value::String(global_string("HELLO, WORLD!".to_owned()))
968 );
969 }
970 }
971
972 #[test]
973 fn test_string_to_lower() {
974 unsafe {
975 let stack = crate::stack::alloc_test_stack();
976 let stack = push(
977 stack,
978 Value::String(global_string("Hello, World!".to_owned())),
979 );
980
981 let stack = string_to_lower(stack);
982
983 let (_stack, result) = pop(stack);
984 assert_eq!(
985 result,
986 Value::String(global_string("hello, world!".to_owned()))
987 );
988 }
989 }
990
991 #[test]
992 fn test_http_header_content_length() {
993 unsafe {
995 let stack = crate::stack::alloc_test_stack();
996 let stack = push(
997 stack,
998 Value::String(global_string("Content-Length: ".to_owned())),
999 );
1000 let stack = push(stack, Value::String(global_string("42".to_owned())));
1001
1002 let stack = string_concat(stack);
1003
1004 let (_stack, result) = pop(stack);
1005 assert_eq!(
1006 result,
1007 Value::String(global_string("Content-Length: 42".to_owned()))
1008 );
1009 }
1010 }
1011
1012 #[test]
1013 fn test_string_equal_true() {
1014 unsafe {
1015 let stack = crate::stack::alloc_test_stack();
1016 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1017 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1018
1019 let stack = string_equal(stack);
1020
1021 let (_stack, result) = pop(stack);
1022 assert_eq!(result, Value::Bool(true));
1023 }
1024 }
1025
1026 #[test]
1027 fn test_string_equal_false() {
1028 unsafe {
1029 let stack = crate::stack::alloc_test_stack();
1030 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1031 let stack = push(stack, Value::String(global_string("world".to_owned())));
1032
1033 let stack = string_equal(stack);
1034
1035 let (_stack, result) = pop(stack);
1036 assert_eq!(result, Value::Bool(false));
1037 }
1038 }
1039
1040 #[test]
1041 fn test_string_equal_empty_strings() {
1042 unsafe {
1043 let stack = crate::stack::alloc_test_stack();
1044 let stack = push(stack, Value::String(global_string("".to_owned())));
1045 let stack = push(stack, Value::String(global_string("".to_owned())));
1046
1047 let stack = string_equal(stack);
1048
1049 let (_stack, result) = pop(stack);
1050 assert_eq!(result, Value::Bool(true));
1051 }
1052 }
1053
1054 #[test]
1057 fn test_string_length_utf8() {
1058 unsafe {
1060 let stack = crate::stack::alloc_test_stack();
1061 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1062
1063 let stack = string_length(stack);
1064
1065 let (_stack, result) = pop(stack);
1066 assert_eq!(result, Value::Int(5)); }
1068 }
1069
1070 #[test]
1071 fn test_string_length_emoji() {
1072 unsafe {
1074 let stack = crate::stack::alloc_test_stack();
1075 let stack = push(stack, Value::String(global_string("hi🎉".to_owned())));
1076
1077 let stack = string_length(stack);
1078
1079 let (_stack, result) = pop(stack);
1080 assert_eq!(result, Value::Int(3)); }
1082 }
1083
1084 #[test]
1085 fn test_string_byte_length_ascii() {
1086 unsafe {
1087 let stack = crate::stack::alloc_test_stack();
1088 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1089
1090 let stack = string_byte_length(stack);
1091
1092 let (_stack, result) = pop(stack);
1093 assert_eq!(result, Value::Int(5)); }
1095 }
1096
1097 #[test]
1098 fn test_string_byte_length_utf8() {
1099 unsafe {
1101 let stack = crate::stack::alloc_test_stack();
1102 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1103
1104 let stack = string_byte_length(stack);
1105
1106 let (_stack, result) = pop(stack);
1107 assert_eq!(result, Value::Int(6)); }
1109 }
1110
1111 #[test]
1112 fn test_string_char_at_ascii() {
1113 unsafe {
1114 let stack = crate::stack::alloc_test_stack();
1115 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1116 let stack = push(stack, Value::Int(0));
1117
1118 let stack = string_char_at(stack);
1119
1120 let (_stack, result) = pop(stack);
1121 assert_eq!(result, Value::Int(104)); }
1123 }
1124
1125 #[test]
1126 fn test_string_char_at_utf8() {
1127 unsafe {
1129 let stack = crate::stack::alloc_test_stack();
1130 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1131 let stack = push(stack, Value::Int(1));
1132
1133 let stack = string_char_at(stack);
1134
1135 let (_stack, result) = pop(stack);
1136 assert_eq!(result, Value::Int(233)); }
1138 }
1139
1140 #[test]
1141 fn test_string_char_at_out_of_bounds() {
1142 unsafe {
1143 let stack = crate::stack::alloc_test_stack();
1144 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1145 let stack = push(stack, Value::Int(10)); let stack = string_char_at(stack);
1148
1149 let (_stack, result) = pop(stack);
1150 assert_eq!(result, Value::Int(-1));
1151 }
1152 }
1153
1154 #[test]
1155 fn test_string_char_at_negative() {
1156 unsafe {
1157 let stack = crate::stack::alloc_test_stack();
1158 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1159 let stack = push(stack, Value::Int(-1));
1160
1161 let stack = string_char_at(stack);
1162
1163 let (_stack, result) = pop(stack);
1164 assert_eq!(result, Value::Int(-1));
1165 }
1166 }
1167
1168 #[test]
1169 fn test_string_substring_simple() {
1170 unsafe {
1171 let stack = crate::stack::alloc_test_stack();
1172 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1173 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1177
1178 let (_stack, result) = pop(stack);
1179 assert_eq!(result, Value::String(global_string("ell".to_owned())));
1180 }
1181 }
1182
1183 #[test]
1184 fn test_string_substring_utf8() {
1185 unsafe {
1187 let stack = crate::stack::alloc_test_stack();
1188 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1189 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1193
1194 let (_stack, result) = pop(stack);
1195 assert_eq!(result, Value::String(global_string("éll".to_owned())));
1196 }
1197 }
1198
1199 #[test]
1200 fn test_string_substring_clamp() {
1201 unsafe {
1203 let stack = crate::stack::alloc_test_stack();
1204 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1205 let stack = push(stack, Value::Int(2)); let stack = push(stack, Value::Int(100)); let stack = string_substring(stack);
1209
1210 let (_stack, result) = pop(stack);
1211 assert_eq!(result, Value::String(global_string("llo".to_owned())));
1212 }
1213 }
1214
1215 #[test]
1216 fn test_string_substring_beyond_end() {
1217 unsafe {
1219 let stack = crate::stack::alloc_test_stack();
1220 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1221 let stack = push(stack, Value::Int(10)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1225
1226 let (_stack, result) = pop(stack);
1227 assert_eq!(result, Value::String(global_string("".to_owned())));
1228 }
1229 }
1230
1231 #[test]
1232 fn test_char_to_string_ascii() {
1233 unsafe {
1234 let stack = crate::stack::alloc_test_stack();
1235 let stack = push(stack, Value::Int(65)); let stack = char_to_string(stack);
1238
1239 let (_stack, result) = pop(stack);
1240 assert_eq!(result, Value::String(global_string("A".to_owned())));
1241 }
1242 }
1243
1244 #[test]
1245 fn test_char_to_string_utf8() {
1246 unsafe {
1247 let stack = crate::stack::alloc_test_stack();
1248 let stack = push(stack, Value::Int(233)); let stack = char_to_string(stack);
1251
1252 let (_stack, result) = pop(stack);
1253 assert_eq!(result, Value::String(global_string("é".to_owned())));
1254 }
1255 }
1256
1257 #[test]
1258 fn test_char_to_string_newline() {
1259 unsafe {
1260 let stack = crate::stack::alloc_test_stack();
1261 let stack = push(stack, Value::Int(10)); let stack = char_to_string(stack);
1264
1265 let (_stack, result) = pop(stack);
1266 assert_eq!(result, Value::String(global_string("\n".to_owned())));
1267 }
1268 }
1269
1270 #[test]
1271 fn test_char_to_string_invalid() {
1272 unsafe {
1273 let stack = crate::stack::alloc_test_stack();
1274 let stack = push(stack, Value::Int(-1)); let stack = char_to_string(stack);
1277
1278 let (_stack, result) = pop(stack);
1279 assert_eq!(result, Value::String(global_string("".to_owned())));
1280 }
1281 }
1282
1283 #[test]
1284 fn test_string_find_found() {
1285 unsafe {
1286 let stack = crate::stack::alloc_test_stack();
1287 let stack = push(
1288 stack,
1289 Value::String(global_string("hello world".to_owned())),
1290 );
1291 let stack = push(stack, Value::String(global_string("world".to_owned())));
1292
1293 let stack = string_find(stack);
1294
1295 let (_stack, result) = pop(stack);
1296 assert_eq!(result, Value::Int(6)); }
1298 }
1299
1300 #[test]
1301 fn test_string_find_not_found() {
1302 unsafe {
1303 let stack = crate::stack::alloc_test_stack();
1304 let stack = push(
1305 stack,
1306 Value::String(global_string("hello world".to_owned())),
1307 );
1308 let stack = push(stack, Value::String(global_string("xyz".to_owned())));
1309
1310 let stack = string_find(stack);
1311
1312 let (_stack, result) = pop(stack);
1313 assert_eq!(result, Value::Int(-1));
1314 }
1315 }
1316
1317 #[test]
1318 fn test_string_find_first_match() {
1319 unsafe {
1321 let stack = crate::stack::alloc_test_stack();
1322 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1323 let stack = push(stack, Value::String(global_string("l".to_owned())));
1324
1325 let stack = string_find(stack);
1326
1327 let (_stack, result) = pop(stack);
1328 assert_eq!(result, Value::Int(2)); }
1330 }
1331
1332 #[test]
1333 fn test_string_find_utf8() {
1334 unsafe {
1336 let stack = crate::stack::alloc_test_stack();
1337 let stack = push(
1338 stack,
1339 Value::String(global_string("héllo wörld".to_owned())),
1340 );
1341 let stack = push(stack, Value::String(global_string("wörld".to_owned())));
1342
1343 let stack = string_find(stack);
1344
1345 let (_stack, result) = pop(stack);
1346 assert_eq!(result, Value::Int(6)); }
1348 }
1349
1350 #[test]
1353 fn test_json_escape_quotes() {
1354 unsafe {
1355 let stack = crate::stack::alloc_test_stack();
1356 let stack = push(
1357 stack,
1358 Value::String(global_string("hello \"world\"".to_owned())),
1359 );
1360
1361 let stack = json_escape(stack);
1362
1363 let (_stack, result) = pop(stack);
1364 assert_eq!(
1365 result,
1366 Value::String(global_string("hello \\\"world\\\"".to_owned()))
1367 );
1368 }
1369 }
1370
1371 #[test]
1372 fn test_json_escape_backslash() {
1373 unsafe {
1374 let stack = crate::stack::alloc_test_stack();
1375 let stack = push(
1376 stack,
1377 Value::String(global_string("path\\to\\file".to_owned())),
1378 );
1379
1380 let stack = json_escape(stack);
1381
1382 let (_stack, result) = pop(stack);
1383 assert_eq!(
1384 result,
1385 Value::String(global_string("path\\\\to\\\\file".to_owned()))
1386 );
1387 }
1388 }
1389
1390 #[test]
1391 fn test_json_escape_newline_tab() {
1392 unsafe {
1393 let stack = crate::stack::alloc_test_stack();
1394 let stack = push(
1395 stack,
1396 Value::String(global_string("line1\nline2\ttabbed".to_owned())),
1397 );
1398
1399 let stack = json_escape(stack);
1400
1401 let (_stack, result) = pop(stack);
1402 assert_eq!(
1403 result,
1404 Value::String(global_string("line1\\nline2\\ttabbed".to_owned()))
1405 );
1406 }
1407 }
1408
1409 #[test]
1410 fn test_json_escape_carriage_return() {
1411 unsafe {
1412 let stack = crate::stack::alloc_test_stack();
1413 let stack = push(
1414 stack,
1415 Value::String(global_string("line1\r\nline2".to_owned())),
1416 );
1417
1418 let stack = json_escape(stack);
1419
1420 let (_stack, result) = pop(stack);
1421 assert_eq!(
1422 result,
1423 Value::String(global_string("line1\\r\\nline2".to_owned()))
1424 );
1425 }
1426 }
1427
1428 #[test]
1429 fn test_json_escape_control_chars() {
1430 unsafe {
1431 let stack = crate::stack::alloc_test_stack();
1432 let stack = push(
1434 stack,
1435 Value::String(global_string("a\x08b\x0Cc".to_owned())),
1436 );
1437
1438 let stack = json_escape(stack);
1439
1440 let (_stack, result) = pop(stack);
1441 assert_eq!(result, Value::String(global_string("a\\bb\\fc".to_owned())));
1442 }
1443 }
1444
1445 #[test]
1446 fn test_json_escape_unicode_control() {
1447 unsafe {
1448 let stack = crate::stack::alloc_test_stack();
1449 let stack = push(stack, Value::String(global_string("a\x00b".to_owned())));
1451
1452 let stack = json_escape(stack);
1453
1454 let (_stack, result) = pop(stack);
1455 assert_eq!(result, Value::String(global_string("a\\u0000b".to_owned())));
1456 }
1457 }
1458
1459 #[test]
1460 fn test_json_escape_mixed_special_chars() {
1461 unsafe {
1463 let stack = crate::stack::alloc_test_stack();
1464 let stack = push(
1465 stack,
1466 Value::String(global_string("Line 1\nLine \"2\"\ttab\r\n".to_owned())),
1467 );
1468
1469 let stack = json_escape(stack);
1470
1471 let (_stack, result) = pop(stack);
1472 assert_eq!(
1473 result,
1474 Value::String(global_string(
1475 "Line 1\\nLine \\\"2\\\"\\ttab\\r\\n".to_owned()
1476 ))
1477 );
1478 }
1479 }
1480
1481 #[test]
1482 fn test_json_escape_no_change() {
1483 unsafe {
1485 let stack = crate::stack::alloc_test_stack();
1486 let stack = push(
1487 stack,
1488 Value::String(global_string("Hello, World!".to_owned())),
1489 );
1490
1491 let stack = json_escape(stack);
1492
1493 let (_stack, result) = pop(stack);
1494 assert_eq!(
1495 result,
1496 Value::String(global_string("Hello, World!".to_owned()))
1497 );
1498 }
1499 }
1500
1501 #[test]
1502 fn test_json_escape_empty_string() {
1503 unsafe {
1504 let stack = crate::stack::alloc_test_stack();
1505 let stack = push(stack, Value::String(global_string("".to_owned())));
1506
1507 let stack = json_escape(stack);
1508
1509 let (_stack, result) = pop(stack);
1510 assert_eq!(result, Value::String(global_string("".to_owned())));
1511 }
1512 }
1513
1514 #[test]
1517 fn test_string_to_int_success() {
1518 unsafe {
1519 let stack = crate::stack::alloc_test_stack();
1520 let stack = push(stack, Value::String(global_string("42".to_owned())));
1521
1522 let stack = string_to_int(stack);
1523
1524 let (stack, success) = pop(stack);
1525 let (_stack, value) = pop(stack);
1526 assert_eq!(success, Value::Bool(true));
1527 assert_eq!(value, Value::Int(42));
1528 }
1529 }
1530
1531 #[test]
1532 fn test_string_to_int_negative() {
1533 unsafe {
1534 let stack = crate::stack::alloc_test_stack();
1535 let stack = push(stack, Value::String(global_string("-99".to_owned())));
1536
1537 let stack = string_to_int(stack);
1538
1539 let (stack, success) = pop(stack);
1540 let (_stack, value) = pop(stack);
1541 assert_eq!(success, Value::Bool(true));
1542 assert_eq!(value, Value::Int(-99));
1543 }
1544 }
1545
1546 #[test]
1547 fn test_string_to_int_with_whitespace() {
1548 unsafe {
1549 let stack = crate::stack::alloc_test_stack();
1550 let stack = push(stack, Value::String(global_string(" 123 ".to_owned())));
1551
1552 let stack = string_to_int(stack);
1553
1554 let (stack, success) = pop(stack);
1555 let (_stack, value) = pop(stack);
1556 assert_eq!(success, Value::Bool(true));
1557 assert_eq!(value, Value::Int(123));
1558 }
1559 }
1560
1561 #[test]
1562 fn test_string_to_int_failure() {
1563 unsafe {
1564 let stack = crate::stack::alloc_test_stack();
1565 let stack = push(
1566 stack,
1567 Value::String(global_string("not a number".to_owned())),
1568 );
1569
1570 let stack = string_to_int(stack);
1571
1572 let (stack, success) = pop(stack);
1573 let (_stack, value) = pop(stack);
1574 assert_eq!(success, Value::Bool(false));
1575 assert_eq!(value, Value::Int(0));
1576 }
1577 }
1578
1579 #[test]
1580 fn test_string_to_int_empty() {
1581 unsafe {
1582 let stack = crate::stack::alloc_test_stack();
1583 let stack = push(stack, Value::String(global_string("".to_owned())));
1584
1585 let stack = string_to_int(stack);
1586
1587 let (stack, success) = pop(stack);
1588 let (_stack, value) = pop(stack);
1589 assert_eq!(success, Value::Bool(false));
1590 assert_eq!(value, Value::Int(0));
1591 }
1592 }
1593
1594 #[test]
1595 fn test_string_to_int_leading_zeros() {
1596 unsafe {
1597 let stack = crate::stack::alloc_test_stack();
1598 let stack = push(stack, Value::String(global_string("007".to_owned())));
1599
1600 let stack = string_to_int(stack);
1601
1602 let (stack, success) = pop(stack);
1603 let (_stack, value) = pop(stack);
1604 assert_eq!(success, Value::Bool(true));
1605 assert_eq!(value, Value::Int(7));
1606 }
1607 }
1608
1609 #[test]
1610 fn test_string_to_int_type_error() {
1611 unsafe {
1612 crate::error::clear_runtime_error();
1613
1614 let stack = crate::stack::alloc_test_stack();
1615 let stack = push(stack, Value::Int(42)); let stack = string_to_int(stack);
1618
1619 assert!(crate::error::has_runtime_error());
1621 let error = crate::error::take_runtime_error().unwrap();
1622 assert!(error.contains("expected String"));
1623
1624 let (stack, success) = pop(stack);
1626 assert_eq!(success, Value::Bool(false));
1627 let (_stack, value) = pop(stack);
1628 assert_eq!(value, Value::Int(0));
1629 }
1630 }
1631}