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
583#[unsafe(no_mangle)]
598pub unsafe extern "C" fn patch_seq_string_join(stack: Stack) -> Stack {
599 unsafe {
600 let (stack, sep_val) = pop(stack);
602 let sep = match &sep_val {
603 Value::String(s) => s.as_str().to_owned(),
604 _ => panic!("string.join: expected String separator, got {:?}", sep_val),
605 };
606
607 let (stack, list_val) = pop(stack);
609 let variant_data = match &list_val {
610 Value::Variant(v) => v,
611 _ => panic!("string.join: expected Variant (list), got {:?}", list_val),
612 };
613
614 let parts: Vec<String> = variant_data
616 .fields
617 .iter()
618 .map(|v| match v {
619 Value::String(s) => s.as_str().to_owned(),
620 Value::Int(n) => n.to_string(),
621 Value::Float(f) => f.to_string(),
622 Value::Bool(b) => if *b { "true" } else { "false" }.to_string(),
623 Value::Symbol(s) => format!(":{}", s.as_str()),
624 _ => format!("{:?}", v),
625 })
626 .collect();
627
628 let result = parts.join(&sep);
629 push(stack, Value::String(global_string(result)))
630 }
631}
632
633pub use patch_seq_char_to_string as char_to_string;
635pub use patch_seq_json_escape as json_escape;
636pub use patch_seq_string_byte_length as string_byte_length;
637pub use patch_seq_string_char_at as string_char_at;
638pub use patch_seq_string_chomp as string_chomp;
639pub use patch_seq_string_concat as string_concat;
640pub use patch_seq_string_contains as string_contains;
641pub use patch_seq_string_empty as string_empty;
642pub use patch_seq_string_equal as string_equal;
643pub use patch_seq_string_find as string_find;
644pub use patch_seq_string_length as string_length;
645pub use patch_seq_string_split as string_split;
646pub use patch_seq_string_starts_with as string_starts_with;
647pub use patch_seq_string_substring as string_substring;
648pub use patch_seq_string_to_int as string_to_int;
649pub use patch_seq_string_to_lower as string_to_lower;
650pub use patch_seq_string_to_upper as string_to_upper;
651pub use patch_seq_string_trim as string_trim;
652
653#[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;
685 use crate::value::Value;
686
687 let val = unsafe { peek(stack) };
689 let s = match &val {
690 Value::String(s) => s,
691 other => panic!(
692 "string_to_cstring: expected String on stack, got {:?}",
693 other
694 ),
695 };
696
697 let str_ptr = s.as_ptr();
698 let len = s.len();
699
700 let alloc_size = len.checked_add(1).unwrap_or_else(|| {
702 panic!(
703 "string_to_cstring: string too large for C conversion (len={})",
704 len
705 )
706 });
707
708 let ptr = unsafe { libc::malloc(alloc_size) as *mut u8 };
710 if ptr.is_null() {
711 panic!("string_to_cstring: malloc failed");
712 }
713
714 unsafe {
716 std::ptr::copy_nonoverlapping(str_ptr, ptr, len);
717 *ptr.add(len) = 0;
719 }
720
721 ptr
722}
723
724#[unsafe(no_mangle)]
733pub unsafe extern "C" fn patch_seq_cstring_to_string(stack: Stack, cstr: *const u8) -> Stack {
734 if cstr.is_null() {
735 return unsafe { push(stack, Value::String(global_string(String::new()))) };
737 }
738
739 let len = unsafe { libc::strlen(cstr as *const libc::c_char) };
741
742 let slice = unsafe { std::slice::from_raw_parts(cstr, len) };
744 let s = String::from_utf8_lossy(slice).into_owned();
745
746 unsafe { push(stack, Value::String(global_string(s))) }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752
753 #[test]
754 fn test_string_split_simple() {
755 unsafe {
756 let stack = crate::stack::alloc_test_stack();
757 let stack = push(stack, Value::String(global_string("a b c".to_owned())));
758 let stack = push(stack, Value::String(global_string(" ".to_owned())));
759
760 let stack = string_split(stack);
761
762 let (_stack, result) = pop(stack);
764 match result {
765 Value::Variant(v) => {
766 assert_eq!(v.tag.as_str(), "List");
767 assert_eq!(v.fields.len(), 3);
768 assert_eq!(v.fields[0], Value::String(global_string("a".to_owned())));
769 assert_eq!(v.fields[1], Value::String(global_string("b".to_owned())));
770 assert_eq!(v.fields[2], Value::String(global_string("c".to_owned())));
771 }
772 _ => panic!("Expected Variant, got {:?}", result),
773 }
774 }
775 }
776
777 #[test]
778 fn test_string_split_empty() {
779 unsafe {
780 let stack = crate::stack::alloc_test_stack();
781 let stack = push(stack, Value::String(global_string("".to_owned())));
782 let stack = push(stack, Value::String(global_string(" ".to_owned())));
783
784 let stack = string_split(stack);
785
786 let (_stack, result) = pop(stack);
788 match result {
789 Value::Variant(v) => {
790 assert_eq!(v.tag.as_str(), "List");
791 assert_eq!(v.fields.len(), 1);
792 assert_eq!(v.fields[0], Value::String(global_string("".to_owned())));
793 }
794 _ => panic!("Expected Variant, got {:?}", result),
795 }
796 }
797 }
798
799 #[test]
800 fn test_string_empty_true() {
801 unsafe {
802 let stack = crate::stack::alloc_test_stack();
803 let stack = push(stack, Value::String(global_string("".to_owned())));
804
805 let stack = string_empty(stack);
806
807 let (_stack, result) = pop(stack);
808 assert_eq!(result, Value::Bool(true));
809 }
810 }
811
812 #[test]
813 fn test_string_empty_false() {
814 unsafe {
815 let stack = crate::stack::alloc_test_stack();
816 let stack = push(stack, Value::String(global_string("hello".to_owned())));
817
818 let stack = string_empty(stack);
819
820 let (_stack, result) = pop(stack);
821 assert_eq!(result, Value::Bool(false));
822 }
823 }
824
825 #[test]
826 fn test_string_contains_true() {
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_contains(stack);
836
837 let (_stack, result) = pop(stack);
838 assert_eq!(result, Value::Bool(true));
839 }
840 }
841
842 #[test]
843 fn test_string_contains_false() {
844 unsafe {
845 let stack = crate::stack::alloc_test_stack();
846 let stack = push(
847 stack,
848 Value::String(global_string("hello world".to_owned())),
849 );
850 let stack = push(stack, Value::String(global_string("foo".to_owned())));
851
852 let stack = string_contains(stack);
853
854 let (_stack, result) = pop(stack);
855 assert_eq!(result, Value::Bool(false));
856 }
857 }
858
859 #[test]
860 fn test_string_starts_with_true() {
861 unsafe {
862 let stack = crate::stack::alloc_test_stack();
863 let stack = push(
864 stack,
865 Value::String(global_string("hello world".to_owned())),
866 );
867 let stack = push(stack, Value::String(global_string("hello".to_owned())));
868
869 let stack = string_starts_with(stack);
870
871 let (_stack, result) = pop(stack);
872 assert_eq!(result, Value::Bool(true));
873 }
874 }
875
876 #[test]
877 fn test_string_starts_with_false() {
878 unsafe {
879 let stack = crate::stack::alloc_test_stack();
880 let stack = push(
881 stack,
882 Value::String(global_string("hello world".to_owned())),
883 );
884 let stack = push(stack, Value::String(global_string("world".to_owned())));
885
886 let stack = string_starts_with(stack);
887
888 let (_stack, result) = pop(stack);
889 assert_eq!(result, Value::Bool(false));
890 }
891 }
892
893 #[test]
894 fn test_http_request_line_parsing() {
895 unsafe {
897 let stack = crate::stack::alloc_test_stack();
898 let stack = push(
899 stack,
900 Value::String(global_string("GET /api/users HTTP/1.1".to_owned())),
901 );
902 let stack = push(stack, Value::String(global_string(" ".to_owned())));
903
904 let stack = string_split(stack);
905
906 let (_stack, result) = pop(stack);
908 match result {
909 Value::Variant(v) => {
910 assert_eq!(v.tag.as_str(), "List");
911 assert_eq!(v.fields.len(), 3);
912 assert_eq!(v.fields[0], Value::String(global_string("GET".to_owned())));
913 assert_eq!(
914 v.fields[1],
915 Value::String(global_string("/api/users".to_owned()))
916 );
917 assert_eq!(
918 v.fields[2],
919 Value::String(global_string("HTTP/1.1".to_owned()))
920 );
921 }
922 _ => panic!("Expected Variant, got {:?}", result),
923 }
924 }
925 }
926
927 #[test]
928 fn test_path_routing() {
929 unsafe {
931 let stack = crate::stack::alloc_test_stack();
932 let stack = push(stack, Value::String(global_string("/api/users".to_owned())));
933 let stack = push(stack, Value::String(global_string("/api/".to_owned())));
934
935 let stack = string_starts_with(stack);
936
937 let (_stack, result) = pop(stack);
938 assert_eq!(result, Value::Bool(true));
939 }
940 }
941
942 #[test]
943 fn test_string_concat() {
944 unsafe {
945 let stack = crate::stack::alloc_test_stack();
946 let stack = push(stack, Value::String(global_string("Hello, ".to_owned())));
947 let stack = push(stack, Value::String(global_string("World!".to_owned())));
948
949 let stack = string_concat(stack);
950
951 let (_stack, result) = pop(stack);
952 assert_eq!(
953 result,
954 Value::String(global_string("Hello, World!".to_owned()))
955 );
956 }
957 }
958
959 #[test]
960 fn test_string_length() {
961 unsafe {
962 let stack = crate::stack::alloc_test_stack();
963 let stack = push(stack, Value::String(global_string("Hello".to_owned())));
964
965 let stack = string_length(stack);
966
967 let (_stack, result) = pop(stack);
968 assert_eq!(result, Value::Int(5));
969 }
970 }
971
972 #[test]
973 fn test_string_length_empty() {
974 unsafe {
975 let stack = crate::stack::alloc_test_stack();
976 let stack = push(stack, Value::String(global_string("".to_owned())));
977
978 let stack = string_length(stack);
979
980 let (_stack, result) = pop(stack);
981 assert_eq!(result, Value::Int(0));
982 }
983 }
984
985 #[test]
986 fn test_string_trim() {
987 unsafe {
988 let stack = crate::stack::alloc_test_stack();
989 let stack = push(
990 stack,
991 Value::String(global_string(" Hello, World! ".to_owned())),
992 );
993
994 let stack = string_trim(stack);
995
996 let (_stack, result) = pop(stack);
997 assert_eq!(
998 result,
999 Value::String(global_string("Hello, World!".to_owned()))
1000 );
1001 }
1002 }
1003
1004 #[test]
1005 fn test_string_to_upper() {
1006 unsafe {
1007 let stack = crate::stack::alloc_test_stack();
1008 let stack = push(
1009 stack,
1010 Value::String(global_string("Hello, World!".to_owned())),
1011 );
1012
1013 let stack = string_to_upper(stack);
1014
1015 let (_stack, result) = pop(stack);
1016 assert_eq!(
1017 result,
1018 Value::String(global_string("HELLO, WORLD!".to_owned()))
1019 );
1020 }
1021 }
1022
1023 #[test]
1024 fn test_string_to_lower() {
1025 unsafe {
1026 let stack = crate::stack::alloc_test_stack();
1027 let stack = push(
1028 stack,
1029 Value::String(global_string("Hello, World!".to_owned())),
1030 );
1031
1032 let stack = string_to_lower(stack);
1033
1034 let (_stack, result) = pop(stack);
1035 assert_eq!(
1036 result,
1037 Value::String(global_string("hello, world!".to_owned()))
1038 );
1039 }
1040 }
1041
1042 #[test]
1043 fn test_http_header_content_length() {
1044 unsafe {
1046 let stack = crate::stack::alloc_test_stack();
1047 let stack = push(
1048 stack,
1049 Value::String(global_string("Content-Length: ".to_owned())),
1050 );
1051 let stack = push(stack, Value::String(global_string("42".to_owned())));
1052
1053 let stack = string_concat(stack);
1054
1055 let (_stack, result) = pop(stack);
1056 assert_eq!(
1057 result,
1058 Value::String(global_string("Content-Length: 42".to_owned()))
1059 );
1060 }
1061 }
1062
1063 #[test]
1064 fn test_string_equal_true() {
1065 unsafe {
1066 let stack = crate::stack::alloc_test_stack();
1067 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1068 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1069
1070 let stack = string_equal(stack);
1071
1072 let (_stack, result) = pop(stack);
1073 assert_eq!(result, Value::Bool(true));
1074 }
1075 }
1076
1077 #[test]
1078 fn test_string_equal_false() {
1079 unsafe {
1080 let stack = crate::stack::alloc_test_stack();
1081 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1082 let stack = push(stack, Value::String(global_string("world".to_owned())));
1083
1084 let stack = string_equal(stack);
1085
1086 let (_stack, result) = pop(stack);
1087 assert_eq!(result, Value::Bool(false));
1088 }
1089 }
1090
1091 #[test]
1092 fn test_string_equal_empty_strings() {
1093 unsafe {
1094 let stack = crate::stack::alloc_test_stack();
1095 let stack = push(stack, Value::String(global_string("".to_owned())));
1096 let stack = push(stack, Value::String(global_string("".to_owned())));
1097
1098 let stack = string_equal(stack);
1099
1100 let (_stack, result) = pop(stack);
1101 assert_eq!(result, Value::Bool(true));
1102 }
1103 }
1104
1105 #[test]
1108 fn test_string_length_utf8() {
1109 unsafe {
1111 let stack = crate::stack::alloc_test_stack();
1112 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1113
1114 let stack = string_length(stack);
1115
1116 let (_stack, result) = pop(stack);
1117 assert_eq!(result, Value::Int(5)); }
1119 }
1120
1121 #[test]
1122 fn test_string_length_emoji() {
1123 unsafe {
1125 let stack = crate::stack::alloc_test_stack();
1126 let stack = push(stack, Value::String(global_string("hi🎉".to_owned())));
1127
1128 let stack = string_length(stack);
1129
1130 let (_stack, result) = pop(stack);
1131 assert_eq!(result, Value::Int(3)); }
1133 }
1134
1135 #[test]
1136 fn test_string_byte_length_ascii() {
1137 unsafe {
1138 let stack = crate::stack::alloc_test_stack();
1139 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1140
1141 let stack = string_byte_length(stack);
1142
1143 let (_stack, result) = pop(stack);
1144 assert_eq!(result, Value::Int(5)); }
1146 }
1147
1148 #[test]
1149 fn test_string_byte_length_utf8() {
1150 unsafe {
1152 let stack = crate::stack::alloc_test_stack();
1153 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1154
1155 let stack = string_byte_length(stack);
1156
1157 let (_stack, result) = pop(stack);
1158 assert_eq!(result, Value::Int(6)); }
1160 }
1161
1162 #[test]
1163 fn test_string_char_at_ascii() {
1164 unsafe {
1165 let stack = crate::stack::alloc_test_stack();
1166 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1167 let stack = push(stack, Value::Int(0));
1168
1169 let stack = string_char_at(stack);
1170
1171 let (_stack, result) = pop(stack);
1172 assert_eq!(result, Value::Int(104)); }
1174 }
1175
1176 #[test]
1177 fn test_string_char_at_utf8() {
1178 unsafe {
1180 let stack = crate::stack::alloc_test_stack();
1181 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1182 let stack = push(stack, Value::Int(1));
1183
1184 let stack = string_char_at(stack);
1185
1186 let (_stack, result) = pop(stack);
1187 assert_eq!(result, Value::Int(233)); }
1189 }
1190
1191 #[test]
1192 fn test_string_char_at_out_of_bounds() {
1193 unsafe {
1194 let stack = crate::stack::alloc_test_stack();
1195 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1196 let stack = push(stack, Value::Int(10)); let stack = string_char_at(stack);
1199
1200 let (_stack, result) = pop(stack);
1201 assert_eq!(result, Value::Int(-1));
1202 }
1203 }
1204
1205 #[test]
1206 fn test_string_char_at_negative() {
1207 unsafe {
1208 let stack = crate::stack::alloc_test_stack();
1209 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1210 let stack = push(stack, Value::Int(-1));
1211
1212 let stack = string_char_at(stack);
1213
1214 let (_stack, result) = pop(stack);
1215 assert_eq!(result, Value::Int(-1));
1216 }
1217 }
1218
1219 #[test]
1220 fn test_string_substring_simple() {
1221 unsafe {
1222 let stack = crate::stack::alloc_test_stack();
1223 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1224 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1228
1229 let (_stack, result) = pop(stack);
1230 assert_eq!(result, Value::String(global_string("ell".to_owned())));
1231 }
1232 }
1233
1234 #[test]
1235 fn test_string_substring_utf8() {
1236 unsafe {
1238 let stack = crate::stack::alloc_test_stack();
1239 let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1240 let stack = push(stack, Value::Int(1)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1244
1245 let (_stack, result) = pop(stack);
1246 assert_eq!(result, Value::String(global_string("éll".to_owned())));
1247 }
1248 }
1249
1250 #[test]
1251 fn test_string_substring_clamp() {
1252 unsafe {
1254 let stack = crate::stack::alloc_test_stack();
1255 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1256 let stack = push(stack, Value::Int(2)); let stack = push(stack, Value::Int(100)); let stack = string_substring(stack);
1260
1261 let (_stack, result) = pop(stack);
1262 assert_eq!(result, Value::String(global_string("llo".to_owned())));
1263 }
1264 }
1265
1266 #[test]
1267 fn test_string_substring_beyond_end() {
1268 unsafe {
1270 let stack = crate::stack::alloc_test_stack();
1271 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1272 let stack = push(stack, Value::Int(10)); let stack = push(stack, Value::Int(3)); let stack = string_substring(stack);
1276
1277 let (_stack, result) = pop(stack);
1278 assert_eq!(result, Value::String(global_string("".to_owned())));
1279 }
1280 }
1281
1282 #[test]
1283 fn test_char_to_string_ascii() {
1284 unsafe {
1285 let stack = crate::stack::alloc_test_stack();
1286 let stack = push(stack, Value::Int(65)); let stack = char_to_string(stack);
1289
1290 let (_stack, result) = pop(stack);
1291 assert_eq!(result, Value::String(global_string("A".to_owned())));
1292 }
1293 }
1294
1295 #[test]
1296 fn test_char_to_string_utf8() {
1297 unsafe {
1298 let stack = crate::stack::alloc_test_stack();
1299 let stack = push(stack, Value::Int(233)); let stack = char_to_string(stack);
1302
1303 let (_stack, result) = pop(stack);
1304 assert_eq!(result, Value::String(global_string("é".to_owned())));
1305 }
1306 }
1307
1308 #[test]
1309 fn test_char_to_string_newline() {
1310 unsafe {
1311 let stack = crate::stack::alloc_test_stack();
1312 let stack = push(stack, Value::Int(10)); let stack = char_to_string(stack);
1315
1316 let (_stack, result) = pop(stack);
1317 assert_eq!(result, Value::String(global_string("\n".to_owned())));
1318 }
1319 }
1320
1321 #[test]
1322 fn test_char_to_string_invalid() {
1323 unsafe {
1324 let stack = crate::stack::alloc_test_stack();
1325 let stack = push(stack, Value::Int(-1)); let stack = char_to_string(stack);
1328
1329 let (_stack, result) = pop(stack);
1330 assert_eq!(result, Value::String(global_string("".to_owned())));
1331 }
1332 }
1333
1334 #[test]
1335 fn test_string_find_found() {
1336 unsafe {
1337 let stack = crate::stack::alloc_test_stack();
1338 let stack = push(
1339 stack,
1340 Value::String(global_string("hello world".to_owned())),
1341 );
1342 let stack = push(stack, Value::String(global_string("world".to_owned())));
1343
1344 let stack = string_find(stack);
1345
1346 let (_stack, result) = pop(stack);
1347 assert_eq!(result, Value::Int(6)); }
1349 }
1350
1351 #[test]
1352 fn test_string_find_not_found() {
1353 unsafe {
1354 let stack = crate::stack::alloc_test_stack();
1355 let stack = push(
1356 stack,
1357 Value::String(global_string("hello world".to_owned())),
1358 );
1359 let stack = push(stack, Value::String(global_string("xyz".to_owned())));
1360
1361 let stack = string_find(stack);
1362
1363 let (_stack, result) = pop(stack);
1364 assert_eq!(result, Value::Int(-1));
1365 }
1366 }
1367
1368 #[test]
1369 fn test_string_find_first_match() {
1370 unsafe {
1372 let stack = crate::stack::alloc_test_stack();
1373 let stack = push(stack, Value::String(global_string("hello".to_owned())));
1374 let stack = push(stack, Value::String(global_string("l".to_owned())));
1375
1376 let stack = string_find(stack);
1377
1378 let (_stack, result) = pop(stack);
1379 assert_eq!(result, Value::Int(2)); }
1381 }
1382
1383 #[test]
1384 fn test_string_find_utf8() {
1385 unsafe {
1387 let stack = crate::stack::alloc_test_stack();
1388 let stack = push(
1389 stack,
1390 Value::String(global_string("héllo wörld".to_owned())),
1391 );
1392 let stack = push(stack, Value::String(global_string("wörld".to_owned())));
1393
1394 let stack = string_find(stack);
1395
1396 let (_stack, result) = pop(stack);
1397 assert_eq!(result, Value::Int(6)); }
1399 }
1400
1401 #[test]
1404 fn test_json_escape_quotes() {
1405 unsafe {
1406 let stack = crate::stack::alloc_test_stack();
1407 let stack = push(
1408 stack,
1409 Value::String(global_string("hello \"world\"".to_owned())),
1410 );
1411
1412 let stack = json_escape(stack);
1413
1414 let (_stack, result) = pop(stack);
1415 assert_eq!(
1416 result,
1417 Value::String(global_string("hello \\\"world\\\"".to_owned()))
1418 );
1419 }
1420 }
1421
1422 #[test]
1423 fn test_json_escape_backslash() {
1424 unsafe {
1425 let stack = crate::stack::alloc_test_stack();
1426 let stack = push(
1427 stack,
1428 Value::String(global_string("path\\to\\file".to_owned())),
1429 );
1430
1431 let stack = json_escape(stack);
1432
1433 let (_stack, result) = pop(stack);
1434 assert_eq!(
1435 result,
1436 Value::String(global_string("path\\\\to\\\\file".to_owned()))
1437 );
1438 }
1439 }
1440
1441 #[test]
1442 fn test_json_escape_newline_tab() {
1443 unsafe {
1444 let stack = crate::stack::alloc_test_stack();
1445 let stack = push(
1446 stack,
1447 Value::String(global_string("line1\nline2\ttabbed".to_owned())),
1448 );
1449
1450 let stack = json_escape(stack);
1451
1452 let (_stack, result) = pop(stack);
1453 assert_eq!(
1454 result,
1455 Value::String(global_string("line1\\nline2\\ttabbed".to_owned()))
1456 );
1457 }
1458 }
1459
1460 #[test]
1461 fn test_json_escape_carriage_return() {
1462 unsafe {
1463 let stack = crate::stack::alloc_test_stack();
1464 let stack = push(
1465 stack,
1466 Value::String(global_string("line1\r\nline2".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("line1\\r\\nline2".to_owned()))
1475 );
1476 }
1477 }
1478
1479 #[test]
1480 fn test_json_escape_control_chars() {
1481 unsafe {
1482 let stack = crate::stack::alloc_test_stack();
1483 let stack = push(
1485 stack,
1486 Value::String(global_string("a\x08b\x0Cc".to_owned())),
1487 );
1488
1489 let stack = json_escape(stack);
1490
1491 let (_stack, result) = pop(stack);
1492 assert_eq!(result, Value::String(global_string("a\\bb\\fc".to_owned())));
1493 }
1494 }
1495
1496 #[test]
1497 fn test_json_escape_unicode_control() {
1498 unsafe {
1499 let stack = crate::stack::alloc_test_stack();
1500 let stack = push(stack, Value::String(global_string("a\x00b".to_owned())));
1502
1503 let stack = json_escape(stack);
1504
1505 let (_stack, result) = pop(stack);
1506 assert_eq!(result, Value::String(global_string("a\\u0000b".to_owned())));
1507 }
1508 }
1509
1510 #[test]
1511 fn test_json_escape_mixed_special_chars() {
1512 unsafe {
1514 let stack = crate::stack::alloc_test_stack();
1515 let stack = push(
1516 stack,
1517 Value::String(global_string("Line 1\nLine \"2\"\ttab\r\n".to_owned())),
1518 );
1519
1520 let stack = json_escape(stack);
1521
1522 let (_stack, result) = pop(stack);
1523 assert_eq!(
1524 result,
1525 Value::String(global_string(
1526 "Line 1\\nLine \\\"2\\\"\\ttab\\r\\n".to_owned()
1527 ))
1528 );
1529 }
1530 }
1531
1532 #[test]
1533 fn test_json_escape_no_change() {
1534 unsafe {
1536 let stack = crate::stack::alloc_test_stack();
1537 let stack = push(
1538 stack,
1539 Value::String(global_string("Hello, World!".to_owned())),
1540 );
1541
1542 let stack = json_escape(stack);
1543
1544 let (_stack, result) = pop(stack);
1545 assert_eq!(
1546 result,
1547 Value::String(global_string("Hello, World!".to_owned()))
1548 );
1549 }
1550 }
1551
1552 #[test]
1553 fn test_json_escape_empty_string() {
1554 unsafe {
1555 let stack = crate::stack::alloc_test_stack();
1556 let stack = push(stack, Value::String(global_string("".to_owned())));
1557
1558 let stack = json_escape(stack);
1559
1560 let (_stack, result) = pop(stack);
1561 assert_eq!(result, Value::String(global_string("".to_owned())));
1562 }
1563 }
1564
1565 #[test]
1568 fn test_string_to_int_success() {
1569 unsafe {
1570 let stack = crate::stack::alloc_test_stack();
1571 let stack = push(stack, Value::String(global_string("42".to_owned())));
1572
1573 let stack = string_to_int(stack);
1574
1575 let (stack, success) = pop(stack);
1576 let (_stack, value) = pop(stack);
1577 assert_eq!(success, Value::Bool(true));
1578 assert_eq!(value, Value::Int(42));
1579 }
1580 }
1581
1582 #[test]
1583 fn test_string_to_int_negative() {
1584 unsafe {
1585 let stack = crate::stack::alloc_test_stack();
1586 let stack = push(stack, Value::String(global_string("-99".to_owned())));
1587
1588 let stack = string_to_int(stack);
1589
1590 let (stack, success) = pop(stack);
1591 let (_stack, value) = pop(stack);
1592 assert_eq!(success, Value::Bool(true));
1593 assert_eq!(value, Value::Int(-99));
1594 }
1595 }
1596
1597 #[test]
1598 fn test_string_to_int_with_whitespace() {
1599 unsafe {
1600 let stack = crate::stack::alloc_test_stack();
1601 let stack = push(stack, Value::String(global_string(" 123 ".to_owned())));
1602
1603 let stack = string_to_int(stack);
1604
1605 let (stack, success) = pop(stack);
1606 let (_stack, value) = pop(stack);
1607 assert_eq!(success, Value::Bool(true));
1608 assert_eq!(value, Value::Int(123));
1609 }
1610 }
1611
1612 #[test]
1613 fn test_string_to_int_failure() {
1614 unsafe {
1615 let stack = crate::stack::alloc_test_stack();
1616 let stack = push(
1617 stack,
1618 Value::String(global_string("not a number".to_owned())),
1619 );
1620
1621 let stack = string_to_int(stack);
1622
1623 let (stack, success) = pop(stack);
1624 let (_stack, value) = pop(stack);
1625 assert_eq!(success, Value::Bool(false));
1626 assert_eq!(value, Value::Int(0));
1627 }
1628 }
1629
1630 #[test]
1631 fn test_string_to_int_empty() {
1632 unsafe {
1633 let stack = crate::stack::alloc_test_stack();
1634 let stack = push(stack, Value::String(global_string("".to_owned())));
1635
1636 let stack = string_to_int(stack);
1637
1638 let (stack, success) = pop(stack);
1639 let (_stack, value) = pop(stack);
1640 assert_eq!(success, Value::Bool(false));
1641 assert_eq!(value, Value::Int(0));
1642 }
1643 }
1644
1645 #[test]
1646 fn test_string_to_int_leading_zeros() {
1647 unsafe {
1648 let stack = crate::stack::alloc_test_stack();
1649 let stack = push(stack, Value::String(global_string("007".to_owned())));
1650
1651 let stack = string_to_int(stack);
1652
1653 let (stack, success) = pop(stack);
1654 let (_stack, value) = pop(stack);
1655 assert_eq!(success, Value::Bool(true));
1656 assert_eq!(value, Value::Int(7));
1657 }
1658 }
1659
1660 #[test]
1661 fn test_string_to_int_type_error() {
1662 unsafe {
1663 crate::error::clear_runtime_error();
1664
1665 let stack = crate::stack::alloc_test_stack();
1666 let stack = push(stack, Value::Int(42)); let stack = string_to_int(stack);
1669
1670 assert!(crate::error::has_runtime_error());
1672 let error = crate::error::take_runtime_error().unwrap();
1673 assert!(error.contains("expected String"));
1674
1675 let (stack, success) = pop(stack);
1677 assert_eq!(success, Value::Bool(false));
1678 let (_stack, value) = pop(stack);
1679 assert_eq!(value, Value::Int(0));
1680 }
1681 }
1682
1683 #[test]
1688 fn test_string_join_strings() {
1689 unsafe {
1690 use crate::value::VariantData;
1691 use std::sync::Arc;
1692
1693 let stack = crate::stack::alloc_test_stack();
1694 let list = Value::Variant(Arc::new(VariantData::new(
1695 global_string("List".to_string()),
1696 vec![
1697 Value::String(global_string("a".to_string())),
1698 Value::String(global_string("b".to_string())),
1699 Value::String(global_string("c".to_string())),
1700 ],
1701 )));
1702 let stack = push(stack, list);
1703 let stack = push(stack, Value::String(global_string(", ".to_string())));
1704 let stack = patch_seq_string_join(stack);
1705
1706 let (_stack, result) = pop(stack);
1707 match result {
1708 Value::String(s) => assert_eq!(s.as_str(), "a, b, c"),
1709 _ => panic!("Expected String, got {:?}", result),
1710 }
1711 }
1712 }
1713
1714 #[test]
1715 fn test_string_join_empty_list() {
1716 unsafe {
1717 use crate::value::VariantData;
1718 use std::sync::Arc;
1719
1720 let stack = crate::stack::alloc_test_stack();
1721 let list = Value::Variant(Arc::new(VariantData::new(
1722 global_string("List".to_string()),
1723 vec![],
1724 )));
1725 let stack = push(stack, list);
1726 let stack = push(stack, Value::String(global_string(", ".to_string())));
1727 let stack = patch_seq_string_join(stack);
1728
1729 let (_stack, result) = pop(stack);
1730 match result {
1731 Value::String(s) => assert_eq!(s.as_str(), ""),
1732 _ => panic!("Expected String"),
1733 }
1734 }
1735 }
1736
1737 #[test]
1738 fn test_string_join_single_element() {
1739 unsafe {
1740 use crate::value::VariantData;
1741 use std::sync::Arc;
1742
1743 let stack = crate::stack::alloc_test_stack();
1744 let list = Value::Variant(Arc::new(VariantData::new(
1745 global_string("List".to_string()),
1746 vec![Value::String(global_string("only".to_string()))],
1747 )));
1748 let stack = push(stack, list);
1749 let stack = push(stack, Value::String(global_string(", ".to_string())));
1750 let stack = patch_seq_string_join(stack);
1751
1752 let (_stack, result) = pop(stack);
1753 match result {
1754 Value::String(s) => assert_eq!(s.as_str(), "only"),
1755 _ => panic!("Expected String"),
1756 }
1757 }
1758 }
1759
1760 #[test]
1761 fn test_string_join_mixed_types() {
1762 unsafe {
1763 use crate::value::VariantData;
1764 use std::sync::Arc;
1765
1766 let stack = crate::stack::alloc_test_stack();
1767 let list = Value::Variant(Arc::new(VariantData::new(
1768 global_string("List".to_string()),
1769 vec![
1770 Value::Int(1),
1771 Value::Bool(true),
1772 Value::String(global_string("x".to_string())),
1773 ],
1774 )));
1775 let stack = push(stack, list);
1776 let stack = push(stack, Value::String(global_string(" ".to_string())));
1777 let stack = patch_seq_string_join(stack);
1778
1779 let (_stack, result) = pop(stack);
1780 match result {
1781 Value::String(s) => assert_eq!(s.as_str(), "1 true x"),
1782 _ => panic!("Expected String"),
1783 }
1784 }
1785 }
1786}