seq_runtime/
string_ops.rs

1//! String operations for Seq
2//!
3//! These functions are exported with C ABI for LLVM codegen to call.
4//!
5//! # Design Decision: split Return Value
6//!
7//! `split` uses Option A (push parts + count):
8//! - "a b c" " " split → "a" "b" "c" 3
9//!
10//! This is the simplest approach, requiring no new types.
11//! The count allows the caller to know how many parts were pushed.
12
13use crate::seqstring::global_string;
14use crate::stack::{Stack, pop, push};
15use crate::value::Value;
16
17/// Split a string on a delimiter
18///
19/// Stack effect: ( str delim -- Variant )
20///
21/// Returns a Variant containing the split parts as fields.
22///
23/// # Safety
24/// Stack must have two String values on top
25#[unsafe(no_mangle)]
26pub unsafe extern "C" fn patch_seq_string_split(stack: Stack) -> Stack {
27    use crate::value::VariantData;
28
29    assert!(!stack.is_null(), "string_split: stack is empty");
30
31    let (stack, delim_val) = unsafe { pop(stack) };
32    assert!(!stack.is_null(), "string_split: need two strings");
33    let (stack, str_val) = unsafe { pop(stack) };
34
35    match (str_val, delim_val) {
36        (Value::String(s), Value::String(d)) => {
37            // Split and collect into Value::String instances
38            let fields: Vec<Value> = s
39                .as_str()
40                .split(d.as_str())
41                .map(|part| Value::String(global_string(part.to_owned())))
42                .collect();
43
44            // Create a Variant with tag 0 and the split parts as fields
45            let variant = Value::Variant(Box::new(VariantData::new(0, fields)));
46
47            unsafe { push(stack, variant) }
48        }
49        _ => panic!("string_split: expected two strings on stack"),
50    }
51}
52
53/// Check if a string is empty
54///
55/// Stack effect: ( str -- bool )
56///
57/// # Safety
58/// Stack must have a String value on top
59#[unsafe(no_mangle)]
60pub unsafe extern "C" fn patch_seq_string_empty(stack: Stack) -> Stack {
61    assert!(!stack.is_null(), "string_empty: stack is empty");
62
63    let (stack, value) = unsafe { pop(stack) };
64
65    match value {
66        Value::String(s) => {
67            let is_empty = s.as_str().is_empty();
68            let result = if is_empty { 1 } else { 0 };
69            unsafe { push(stack, Value::Int(result)) }
70        }
71        _ => panic!("string_empty: expected String on stack"),
72    }
73}
74
75/// Check if a string contains a substring
76///
77/// Stack effect: ( str substring -- bool )
78///
79/// # Safety
80/// Stack must have two String values on top
81#[unsafe(no_mangle)]
82pub unsafe extern "C" fn patch_seq_string_contains(stack: Stack) -> Stack {
83    assert!(!stack.is_null(), "string_contains: stack is empty");
84
85    let (stack, substring_val) = unsafe { pop(stack) };
86    assert!(!stack.is_null(), "string_contains: need two strings");
87    let (stack, str_val) = unsafe { pop(stack) };
88
89    match (str_val, substring_val) {
90        (Value::String(s), Value::String(sub)) => {
91            let contains = s.as_str().contains(sub.as_str());
92            let result = if contains { 1 } else { 0 };
93            unsafe { push(stack, Value::Int(result)) }
94        }
95        _ => panic!("string_contains: expected two strings on stack"),
96    }
97}
98
99/// Check if a string starts with a prefix
100///
101/// Stack effect: ( str prefix -- bool )
102///
103/// # Safety
104/// Stack must have two String values on top
105#[unsafe(no_mangle)]
106pub unsafe extern "C" fn patch_seq_string_starts_with(stack: Stack) -> Stack {
107    assert!(!stack.is_null(), "string_starts_with: stack is empty");
108
109    let (stack, prefix_val) = unsafe { pop(stack) };
110    assert!(!stack.is_null(), "string_starts_with: need two strings");
111    let (stack, str_val) = unsafe { pop(stack) };
112
113    match (str_val, prefix_val) {
114        (Value::String(s), Value::String(prefix)) => {
115            let starts = s.as_str().starts_with(prefix.as_str());
116            let result = if starts { 1 } else { 0 };
117            unsafe { push(stack, Value::Int(result)) }
118        }
119        _ => panic!("string_starts_with: expected two strings on stack"),
120    }
121}
122
123/// Concatenate two strings
124///
125/// Stack effect: ( str1 str2 -- result )
126///
127/// # Safety
128/// Stack must have two String values on top
129#[unsafe(no_mangle)]
130pub unsafe extern "C" fn patch_seq_string_concat(stack: Stack) -> Stack {
131    assert!(!stack.is_null(), "string_concat: stack is empty");
132
133    let (stack, str2_val) = unsafe { pop(stack) };
134    assert!(!stack.is_null(), "string_concat: need two strings");
135    let (stack, str1_val) = unsafe { pop(stack) };
136
137    match (str1_val, str2_val) {
138        (Value::String(s1), Value::String(s2)) => {
139            let result = format!("{}{}", s1.as_str(), s2.as_str());
140            unsafe { push(stack, Value::String(global_string(result))) }
141        }
142        _ => panic!("string_concat: expected two strings on stack"),
143    }
144}
145
146/// Get the length of a string in Unicode characters (code points)
147///
148/// Stack effect: ( str -- int )
149///
150/// Note: This returns character count, not byte count.
151/// For UTF-8 byte length (e.g., HTTP Content-Length), use `string-byte-length`.
152///
153/// # Safety
154/// Stack must have a String value on top
155#[unsafe(no_mangle)]
156pub unsafe extern "C" fn patch_seq_string_length(stack: Stack) -> Stack {
157    assert!(!stack.is_null(), "string_length: stack is empty");
158
159    let (stack, str_val) = unsafe { pop(stack) };
160
161    match str_val {
162        Value::String(s) => {
163            let len = s.as_str().chars().count() as i64;
164            unsafe { push(stack, Value::Int(len)) }
165        }
166        _ => panic!("string_length: expected String on stack"),
167    }
168}
169
170/// Get the byte length of a string (UTF-8 encoded)
171///
172/// Stack effect: ( str -- int )
173///
174/// Use this for HTTP Content-Length headers and buffer allocation.
175///
176/// # Safety
177/// Stack must have a String value on top
178#[unsafe(no_mangle)]
179pub unsafe extern "C" fn patch_seq_string_byte_length(stack: Stack) -> Stack {
180    assert!(!stack.is_null(), "string_byte_length: stack is empty");
181
182    let (stack, str_val) = unsafe { pop(stack) };
183
184    match str_val {
185        Value::String(s) => {
186            let len = s.as_str().len() as i64;
187            unsafe { push(stack, Value::Int(len)) }
188        }
189        _ => panic!("string_byte_length: expected String on stack"),
190    }
191}
192
193/// Get the Unicode code point at a character index
194///
195/// Stack effect: ( str int -- int )
196///
197/// Returns the code point value at the given character index.
198/// Returns -1 if index is out of bounds.
199///
200/// # Safety
201/// Stack must have a String and Int on top
202#[unsafe(no_mangle)]
203pub unsafe extern "C" fn patch_seq_string_char_at(stack: Stack) -> Stack {
204    assert!(!stack.is_null(), "string_char_at: stack is empty");
205
206    let (stack, index_val) = unsafe { pop(stack) };
207    assert!(!stack.is_null(), "string_char_at: need string and index");
208    let (stack, str_val) = unsafe { pop(stack) };
209
210    match (str_val, index_val) {
211        (Value::String(s), Value::Int(index)) => {
212            let result = if index < 0 {
213                -1
214            } else {
215                s.as_str()
216                    .chars()
217                    .nth(index as usize)
218                    .map(|c| c as i64)
219                    .unwrap_or(-1)
220            };
221            unsafe { push(stack, Value::Int(result)) }
222        }
223        _ => panic!("string_char_at: expected String and Int on stack"),
224    }
225}
226
227/// Extract a substring using character indices
228///
229/// Stack effect: ( str start len -- str )
230///
231/// Arguments:
232/// - str: The source string
233/// - start: Starting character index
234/// - len: Number of characters to extract
235///
236/// Edge cases:
237/// - Start beyond end: returns empty string
238/// - Length extends past end: clamps to available
239///
240/// # Safety
241/// Stack must have String, Int, Int on top
242#[unsafe(no_mangle)]
243pub unsafe extern "C" fn patch_seq_string_substring(stack: Stack) -> Stack {
244    assert!(!stack.is_null(), "string_substring: stack is empty");
245
246    let (stack, len_val) = unsafe { pop(stack) };
247    assert!(
248        !stack.is_null(),
249        "string_substring: need string, start, len"
250    );
251    let (stack, start_val) = unsafe { pop(stack) };
252    assert!(
253        !stack.is_null(),
254        "string_substring: need string, start, len"
255    );
256    let (stack, str_val) = unsafe { pop(stack) };
257
258    match (str_val, start_val, len_val) {
259        (Value::String(s), Value::Int(start), Value::Int(len)) => {
260            let result = if start < 0 || len < 0 {
261                String::new()
262            } else {
263                s.as_str()
264                    .chars()
265                    .skip(start as usize)
266                    .take(len as usize)
267                    .collect()
268            };
269            unsafe { push(stack, Value::String(global_string(result))) }
270        }
271        _ => panic!("string_substring: expected String, Int, Int on stack"),
272    }
273}
274
275/// Convert a Unicode code point to a single-character string
276///
277/// Stack effect: ( int -- str )
278///
279/// Creates a string containing the single character represented by the code point.
280/// Panics if the code point is invalid.
281///
282/// # Safety
283/// Stack must have an Int on top
284#[unsafe(no_mangle)]
285pub unsafe extern "C" fn patch_seq_char_to_string(stack: Stack) -> Stack {
286    assert!(!stack.is_null(), "char_to_string: stack is empty");
287
288    let (stack, code_point_val) = unsafe { pop(stack) };
289
290    match code_point_val {
291        Value::Int(code_point) => {
292            let result = if !(0..=0x10FFFF).contains(&code_point) {
293                // Invalid code point - return empty string
294                String::new()
295            } else {
296                match char::from_u32(code_point as u32) {
297                    Some(c) => c.to_string(),
298                    None => String::new(), // Invalid code point (e.g., surrogate)
299                }
300            };
301            unsafe { push(stack, Value::String(global_string(result))) }
302        }
303        _ => panic!("char_to_string: expected Int on stack"),
304    }
305}
306
307/// Find the first occurrence of a substring
308///
309/// Stack effect: ( str needle -- int )
310///
311/// Returns the character index of the first occurrence of needle in str.
312/// Returns -1 if not found.
313///
314/// # Safety
315/// Stack must have two Strings on top
316#[unsafe(no_mangle)]
317pub unsafe extern "C" fn patch_seq_string_find(stack: Stack) -> Stack {
318    assert!(!stack.is_null(), "string_find: stack is empty");
319
320    let (stack, needle_val) = unsafe { pop(stack) };
321    assert!(!stack.is_null(), "string_find: need string and needle");
322    let (stack, str_val) = unsafe { pop(stack) };
323
324    match (str_val, needle_val) {
325        (Value::String(haystack), Value::String(needle)) => {
326            let haystack_str = haystack.as_str();
327            let needle_str = needle.as_str();
328
329            // Find byte position then convert to character position
330            let result = match haystack_str.find(needle_str) {
331                Some(byte_pos) => {
332                    // Count characters up to byte_pos
333                    haystack_str[..byte_pos].chars().count() as i64
334                }
335                None => -1,
336            };
337            unsafe { push(stack, Value::Int(result)) }
338        }
339        _ => panic!("string_find: expected two Strings on stack"),
340    }
341}
342
343/// Trim whitespace from both ends of a string
344///
345/// Stack effect: ( str -- trimmed )
346///
347/// # Safety
348/// Stack must have a String value on top
349#[unsafe(no_mangle)]
350pub unsafe extern "C" fn patch_seq_string_trim(stack: Stack) -> Stack {
351    assert!(!stack.is_null(), "string_trim: stack is empty");
352
353    let (stack, str_val) = unsafe { pop(stack) };
354
355    match str_val {
356        Value::String(s) => {
357            let trimmed = s.as_str().trim();
358            unsafe { push(stack, Value::String(global_string(trimmed.to_owned()))) }
359        }
360        _ => panic!("string_trim: expected String on stack"),
361    }
362}
363
364/// Convert a string to uppercase
365///
366/// Stack effect: ( str -- upper )
367///
368/// # Safety
369/// Stack must have a String value on top
370#[unsafe(no_mangle)]
371pub unsafe extern "C" fn patch_seq_string_to_upper(stack: Stack) -> Stack {
372    assert!(!stack.is_null(), "string_to_upper: stack is empty");
373
374    let (stack, str_val) = unsafe { pop(stack) };
375
376    match str_val {
377        Value::String(s) => {
378            let upper = s.as_str().to_uppercase();
379            unsafe { push(stack, Value::String(global_string(upper))) }
380        }
381        _ => panic!("string_to_upper: expected String on stack"),
382    }
383}
384
385/// Convert a string to lowercase
386///
387/// Stack effect: ( str -- lower )
388///
389/// # Safety
390/// Stack must have a String value on top
391#[unsafe(no_mangle)]
392pub unsafe extern "C" fn patch_seq_string_to_lower(stack: Stack) -> Stack {
393    assert!(!stack.is_null(), "string_to_lower: stack is empty");
394
395    let (stack, str_val) = unsafe { pop(stack) };
396
397    match str_val {
398        Value::String(s) => {
399            let lower = s.as_str().to_lowercase();
400            unsafe { push(stack, Value::String(global_string(lower))) }
401        }
402        _ => panic!("string_to_lower: expected String on stack"),
403    }
404}
405
406/// Check if two strings are equal
407///
408/// Stack effect: ( str1 str2 -- bool )
409///
410/// # Safety
411/// Stack must have two String values on top
412#[unsafe(no_mangle)]
413pub unsafe extern "C" fn patch_seq_string_equal(stack: Stack) -> Stack {
414    assert!(!stack.is_null(), "string_equal: stack is empty");
415
416    let (stack, str2_val) = unsafe { pop(stack) };
417    assert!(!stack.is_null(), "string_equal: need two strings");
418    let (stack, str1_val) = unsafe { pop(stack) };
419
420    match (str1_val, str2_val) {
421        (Value::String(s1), Value::String(s2)) => {
422            let equal = s1.as_str() == s2.as_str();
423            let result = if equal { 1 } else { 0 };
424            unsafe { push(stack, Value::Int(result)) }
425        }
426        _ => panic!("string_equal: expected two strings on stack"),
427    }
428}
429
430/// Escape a string for JSON output
431///
432/// Stack effect: ( str -- str )
433///
434/// Escapes special characters according to JSON spec:
435/// - `"` → `\"`
436/// - `\` → `\\`
437/// - newline → `\n`
438/// - carriage return → `\r`
439/// - tab → `\t`
440/// - backspace → `\b`
441/// - form feed → `\f`
442/// - Control characters (0x00-0x1F) → `\uXXXX`
443///
444/// # Safety
445/// Stack must have a String value on top
446#[unsafe(no_mangle)]
447pub unsafe extern "C" fn patch_seq_json_escape(stack: Stack) -> Stack {
448    assert!(!stack.is_null(), "json_escape: stack is empty");
449
450    let (stack, value) = unsafe { pop(stack) };
451
452    match value {
453        Value::String(s) => {
454            let input = s.as_str();
455            let mut result = String::with_capacity(input.len() + 16);
456
457            for ch in input.chars() {
458                match ch {
459                    '"' => result.push_str("\\\""),
460                    '\\' => result.push_str("\\\\"),
461                    '\n' => result.push_str("\\n"),
462                    '\r' => result.push_str("\\r"),
463                    '\t' => result.push_str("\\t"),
464                    '\x08' => result.push_str("\\b"), // backspace
465                    '\x0C' => result.push_str("\\f"), // form feed
466                    // Control characters (0x00-0x1F except those handled above)
467                    // RFC 8259 uses uppercase hex in examples for Unicode escapes
468                    c if c.is_control() => {
469                        result.push_str(&format!("\\u{:04X}", c as u32));
470                    }
471                    c => result.push(c),
472                }
473            }
474
475            unsafe { push(stack, Value::String(global_string(result))) }
476        }
477        _ => panic!("json_escape: expected String on stack"),
478    }
479}
480
481/// Convert String to Int: ( String -- Int Int )
482/// Returns the parsed int and 1 on success, or 0 and 0 on failure.
483/// Accepts integers in range [-9223372036854775808, 9223372036854775807] (i64).
484/// Trims leading/trailing whitespace before parsing.
485/// Leading zeros are accepted (e.g., "007" parses to 7).
486///
487/// # Safety
488/// Stack must have a String value on top
489#[unsafe(no_mangle)]
490pub unsafe extern "C" fn patch_seq_string_to_int(stack: Stack) -> Stack {
491    assert!(!stack.is_null(), "string->int: stack is empty");
492    let (stack, val) = unsafe { pop(stack) };
493
494    match val {
495        Value::String(s) => match s.as_str().trim().parse::<i64>() {
496            Ok(i) => {
497                let stack = unsafe { push(stack, Value::Int(i)) };
498                unsafe { push(stack, Value::Int(1)) }
499            }
500            Err(_) => {
501                let stack = unsafe { push(stack, Value::Int(0)) };
502                unsafe { push(stack, Value::Int(0)) }
503            }
504        },
505        _ => panic!("string->int: expected String on stack"),
506    }
507}
508
509/// Remove trailing newline characters from a string
510///
511/// Stack effect: ( str -- str )
512///
513/// Removes trailing \n or \r\n (handles both Unix and Windows line endings).
514/// If the string doesn't end with a newline, returns it unchanged.
515///
516/// # Safety
517/// Stack must have a String value on top
518#[unsafe(no_mangle)]
519pub unsafe extern "C" fn patch_seq_string_chomp(stack: Stack) -> Stack {
520    assert!(!stack.is_null(), "string_chomp: stack is empty");
521
522    let (stack, str_val) = unsafe { pop(stack) };
523
524    match str_val {
525        Value::String(s) => {
526            let mut result = s.as_str().to_owned();
527            if result.ends_with('\n') {
528                result.pop();
529                if result.ends_with('\r') {
530                    result.pop();
531                }
532            }
533            unsafe { push(stack, Value::String(global_string(result))) }
534        }
535        _ => panic!("string_chomp: expected String on stack"),
536    }
537}
538
539// Public re-exports with short names for internal use
540pub use patch_seq_char_to_string as char_to_string;
541pub use patch_seq_json_escape as json_escape;
542pub use patch_seq_string_byte_length as string_byte_length;
543pub use patch_seq_string_char_at as string_char_at;
544pub use patch_seq_string_chomp as string_chomp;
545pub use patch_seq_string_concat as string_concat;
546pub use patch_seq_string_contains as string_contains;
547pub use patch_seq_string_empty as string_empty;
548pub use patch_seq_string_equal as string_equal;
549pub use patch_seq_string_find as string_find;
550pub use patch_seq_string_length as string_length;
551pub use patch_seq_string_split as string_split;
552pub use patch_seq_string_starts_with as string_starts_with;
553pub use patch_seq_string_substring as string_substring;
554pub use patch_seq_string_to_int as string_to_int;
555pub use patch_seq_string_to_lower as string_to_lower;
556pub use patch_seq_string_to_upper as string_to_upper;
557pub use patch_seq_string_trim as string_trim;
558
559#[cfg(test)]
560mod tests {
561    use super::*;
562
563    #[test]
564    fn test_string_split_simple() {
565        unsafe {
566            let stack = std::ptr::null_mut();
567            let stack = push(stack, Value::String(global_string("a b c".to_owned())));
568            let stack = push(stack, Value::String(global_string(" ".to_owned())));
569
570            let stack = string_split(stack);
571
572            // Should have a Variant with 3 fields: "a", "b", "c"
573            let (stack, result) = pop(stack);
574            match result {
575                Value::Variant(v) => {
576                    assert_eq!(v.tag, 0);
577                    assert_eq!(v.fields.len(), 3);
578                    assert_eq!(v.fields[0], Value::String(global_string("a".to_owned())));
579                    assert_eq!(v.fields[1], Value::String(global_string("b".to_owned())));
580                    assert_eq!(v.fields[2], Value::String(global_string("c".to_owned())));
581                }
582                _ => panic!("Expected Variant, got {:?}", result),
583            }
584
585            assert!(stack.is_null());
586        }
587    }
588
589    #[test]
590    fn test_string_split_empty() {
591        unsafe {
592            let stack = std::ptr::null_mut();
593            let stack = push(stack, Value::String(global_string("".to_owned())));
594            let stack = push(stack, Value::String(global_string(" ".to_owned())));
595
596            let stack = string_split(stack);
597
598            // Empty string splits to one empty part
599            let (stack, result) = pop(stack);
600            match result {
601                Value::Variant(v) => {
602                    assert_eq!(v.tag, 0);
603                    assert_eq!(v.fields.len(), 1);
604                    assert_eq!(v.fields[0], Value::String(global_string("".to_owned())));
605                }
606                _ => panic!("Expected Variant, got {:?}", result),
607            }
608
609            assert!(stack.is_null());
610        }
611    }
612
613    #[test]
614    fn test_string_empty_true() {
615        unsafe {
616            let stack = std::ptr::null_mut();
617            let stack = push(stack, Value::String(global_string("".to_owned())));
618
619            let stack = string_empty(stack);
620
621            let (stack, result) = pop(stack);
622            assert_eq!(result, Value::Int(1));
623            assert!(stack.is_null());
624        }
625    }
626
627    #[test]
628    fn test_string_empty_false() {
629        unsafe {
630            let stack = std::ptr::null_mut();
631            let stack = push(stack, Value::String(global_string("hello".to_owned())));
632
633            let stack = string_empty(stack);
634
635            let (stack, result) = pop(stack);
636            assert_eq!(result, Value::Int(0));
637            assert!(stack.is_null());
638        }
639    }
640
641    #[test]
642    fn test_string_contains_true() {
643        unsafe {
644            let stack = std::ptr::null_mut();
645            let stack = push(
646                stack,
647                Value::String(global_string("hello world".to_owned())),
648            );
649            let stack = push(stack, Value::String(global_string("world".to_owned())));
650
651            let stack = string_contains(stack);
652
653            let (stack, result) = pop(stack);
654            assert_eq!(result, Value::Int(1));
655            assert!(stack.is_null());
656        }
657    }
658
659    #[test]
660    fn test_string_contains_false() {
661        unsafe {
662            let stack = std::ptr::null_mut();
663            let stack = push(
664                stack,
665                Value::String(global_string("hello world".to_owned())),
666            );
667            let stack = push(stack, Value::String(global_string("foo".to_owned())));
668
669            let stack = string_contains(stack);
670
671            let (stack, result) = pop(stack);
672            assert_eq!(result, Value::Int(0));
673            assert!(stack.is_null());
674        }
675    }
676
677    #[test]
678    fn test_string_starts_with_true() {
679        unsafe {
680            let stack = std::ptr::null_mut();
681            let stack = push(
682                stack,
683                Value::String(global_string("hello world".to_owned())),
684            );
685            let stack = push(stack, Value::String(global_string("hello".to_owned())));
686
687            let stack = string_starts_with(stack);
688
689            let (stack, result) = pop(stack);
690            assert_eq!(result, Value::Int(1));
691            assert!(stack.is_null());
692        }
693    }
694
695    #[test]
696    fn test_string_starts_with_false() {
697        unsafe {
698            let stack = std::ptr::null_mut();
699            let stack = push(
700                stack,
701                Value::String(global_string("hello world".to_owned())),
702            );
703            let stack = push(stack, Value::String(global_string("world".to_owned())));
704
705            let stack = string_starts_with(stack);
706
707            let (stack, result) = pop(stack);
708            assert_eq!(result, Value::Int(0));
709            assert!(stack.is_null());
710        }
711    }
712
713    #[test]
714    fn test_http_request_line_parsing() {
715        // Real-world use case: Parse "GET /api/users HTTP/1.1"
716        unsafe {
717            let stack = std::ptr::null_mut();
718            let stack = push(
719                stack,
720                Value::String(global_string("GET /api/users HTTP/1.1".to_owned())),
721            );
722            let stack = push(stack, Value::String(global_string(" ".to_owned())));
723
724            let stack = string_split(stack);
725
726            // Should have a Variant with 3 fields: "GET", "/api/users", "HTTP/1.1"
727            let (stack, result) = pop(stack);
728            match result {
729                Value::Variant(v) => {
730                    assert_eq!(v.tag, 0);
731                    assert_eq!(v.fields.len(), 3);
732                    assert_eq!(v.fields[0], Value::String(global_string("GET".to_owned())));
733                    assert_eq!(
734                        v.fields[1],
735                        Value::String(global_string("/api/users".to_owned()))
736                    );
737                    assert_eq!(
738                        v.fields[2],
739                        Value::String(global_string("HTTP/1.1".to_owned()))
740                    );
741                }
742                _ => panic!("Expected Variant, got {:?}", result),
743            }
744
745            assert!(stack.is_null());
746        }
747    }
748
749    #[test]
750    fn test_path_routing() {
751        // Real-world use case: Check if path starts with "/api/"
752        unsafe {
753            let stack = std::ptr::null_mut();
754            let stack = push(stack, Value::String(global_string("/api/users".to_owned())));
755            let stack = push(stack, Value::String(global_string("/api/".to_owned())));
756
757            let stack = string_starts_with(stack);
758
759            let (stack, result) = pop(stack);
760            assert_eq!(result, Value::Int(1));
761            assert!(stack.is_null());
762        }
763    }
764
765    #[test]
766    fn test_string_concat() {
767        unsafe {
768            let stack = std::ptr::null_mut();
769            let stack = push(stack, Value::String(global_string("Hello, ".to_owned())));
770            let stack = push(stack, Value::String(global_string("World!".to_owned())));
771
772            let stack = string_concat(stack);
773
774            let (stack, result) = pop(stack);
775            assert_eq!(
776                result,
777                Value::String(global_string("Hello, World!".to_owned()))
778            );
779            assert!(stack.is_null());
780        }
781    }
782
783    #[test]
784    fn test_string_length() {
785        unsafe {
786            let stack = std::ptr::null_mut();
787            let stack = push(stack, Value::String(global_string("Hello".to_owned())));
788
789            let stack = string_length(stack);
790
791            let (stack, result) = pop(stack);
792            assert_eq!(result, Value::Int(5));
793            assert!(stack.is_null());
794        }
795    }
796
797    #[test]
798    fn test_string_length_empty() {
799        unsafe {
800            let stack = std::ptr::null_mut();
801            let stack = push(stack, Value::String(global_string("".to_owned())));
802
803            let stack = string_length(stack);
804
805            let (stack, result) = pop(stack);
806            assert_eq!(result, Value::Int(0));
807            assert!(stack.is_null());
808        }
809    }
810
811    #[test]
812    fn test_string_trim() {
813        unsafe {
814            let stack = std::ptr::null_mut();
815            let stack = push(
816                stack,
817                Value::String(global_string("  Hello, World!  ".to_owned())),
818            );
819
820            let stack = string_trim(stack);
821
822            let (stack, result) = pop(stack);
823            assert_eq!(
824                result,
825                Value::String(global_string("Hello, World!".to_owned()))
826            );
827            assert!(stack.is_null());
828        }
829    }
830
831    #[test]
832    fn test_string_to_upper() {
833        unsafe {
834            let stack = std::ptr::null_mut();
835            let stack = push(
836                stack,
837                Value::String(global_string("Hello, World!".to_owned())),
838            );
839
840            let stack = string_to_upper(stack);
841
842            let (stack, result) = pop(stack);
843            assert_eq!(
844                result,
845                Value::String(global_string("HELLO, WORLD!".to_owned()))
846            );
847            assert!(stack.is_null());
848        }
849    }
850
851    #[test]
852    fn test_string_to_lower() {
853        unsafe {
854            let stack = std::ptr::null_mut();
855            let stack = push(
856                stack,
857                Value::String(global_string("Hello, World!".to_owned())),
858            );
859
860            let stack = string_to_lower(stack);
861
862            let (stack, result) = pop(stack);
863            assert_eq!(
864                result,
865                Value::String(global_string("hello, world!".to_owned()))
866            );
867            assert!(stack.is_null());
868        }
869    }
870
871    #[test]
872    fn test_http_header_content_length() {
873        // Real-world use case: Build "Content-Length: 42" header
874        unsafe {
875            let stack = std::ptr::null_mut();
876            let stack = push(
877                stack,
878                Value::String(global_string("Content-Length: ".to_owned())),
879            );
880            let stack = push(stack, Value::String(global_string("42".to_owned())));
881
882            let stack = string_concat(stack);
883
884            let (stack, result) = pop(stack);
885            assert_eq!(
886                result,
887                Value::String(global_string("Content-Length: 42".to_owned()))
888            );
889            assert!(stack.is_null());
890        }
891    }
892
893    #[test]
894    fn test_string_equal_true() {
895        unsafe {
896            let stack = std::ptr::null_mut();
897            let stack = push(stack, Value::String(global_string("hello".to_owned())));
898            let stack = push(stack, Value::String(global_string("hello".to_owned())));
899
900            let stack = string_equal(stack);
901
902            let (stack, result) = pop(stack);
903            assert_eq!(result, Value::Int(1));
904            assert!(stack.is_null());
905        }
906    }
907
908    #[test]
909    fn test_string_equal_false() {
910        unsafe {
911            let stack = std::ptr::null_mut();
912            let stack = push(stack, Value::String(global_string("hello".to_owned())));
913            let stack = push(stack, Value::String(global_string("world".to_owned())));
914
915            let stack = string_equal(stack);
916
917            let (stack, result) = pop(stack);
918            assert_eq!(result, Value::Int(0));
919            assert!(stack.is_null());
920        }
921    }
922
923    #[test]
924    fn test_string_equal_empty_strings() {
925        unsafe {
926            let stack = std::ptr::null_mut();
927            let stack = push(stack, Value::String(global_string("".to_owned())));
928            let stack = push(stack, Value::String(global_string("".to_owned())));
929
930            let stack = string_equal(stack);
931
932            let (stack, result) = pop(stack);
933            assert_eq!(result, Value::Int(1));
934            assert!(stack.is_null());
935        }
936    }
937
938    // UTF-8 String Primitives Tests
939
940    #[test]
941    fn test_string_length_utf8() {
942        // "héllo" has 5 characters but 6 bytes (é is 2 bytes in UTF-8)
943        unsafe {
944            let stack = std::ptr::null_mut();
945            let stack = push(stack, Value::String(global_string("héllo".to_owned())));
946
947            let stack = string_length(stack);
948
949            let (stack, result) = pop(stack);
950            assert_eq!(result, Value::Int(5)); // Characters, not bytes
951            assert!(stack.is_null());
952        }
953    }
954
955    #[test]
956    fn test_string_length_emoji() {
957        // Emoji is one code point but multiple bytes
958        unsafe {
959            let stack = std::ptr::null_mut();
960            let stack = push(stack, Value::String(global_string("hi🎉".to_owned())));
961
962            let stack = string_length(stack);
963
964            let (stack, result) = pop(stack);
965            assert_eq!(result, Value::Int(3)); // 'h', 'i', and emoji
966            assert!(stack.is_null());
967        }
968    }
969
970    #[test]
971    fn test_string_byte_length_ascii() {
972        unsafe {
973            let stack = std::ptr::null_mut();
974            let stack = push(stack, Value::String(global_string("hello".to_owned())));
975
976            let stack = string_byte_length(stack);
977
978            let (stack, result) = pop(stack);
979            assert_eq!(result, Value::Int(5)); // Same as char length for ASCII
980            assert!(stack.is_null());
981        }
982    }
983
984    #[test]
985    fn test_string_byte_length_utf8() {
986        // "héllo" has 5 characters but 6 bytes
987        unsafe {
988            let stack = std::ptr::null_mut();
989            let stack = push(stack, Value::String(global_string("héllo".to_owned())));
990
991            let stack = string_byte_length(stack);
992
993            let (stack, result) = pop(stack);
994            assert_eq!(result, Value::Int(6)); // Bytes, not characters
995            assert!(stack.is_null());
996        }
997    }
998
999    #[test]
1000    fn test_string_char_at_ascii() {
1001        unsafe {
1002            let stack = std::ptr::null_mut();
1003            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1004            let stack = push(stack, Value::Int(0));
1005
1006            let stack = string_char_at(stack);
1007
1008            let (stack, result) = pop(stack);
1009            assert_eq!(result, Value::Int(104)); // 'h' = 104
1010            assert!(stack.is_null());
1011        }
1012    }
1013
1014    #[test]
1015    fn test_string_char_at_utf8() {
1016        // Get the é character at index 1 in "héllo"
1017        unsafe {
1018            let stack = std::ptr::null_mut();
1019            let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1020            let stack = push(stack, Value::Int(1));
1021
1022            let stack = string_char_at(stack);
1023
1024            let (stack, result) = pop(stack);
1025            assert_eq!(result, Value::Int(233)); // 'é' = U+00E9 = 233
1026            assert!(stack.is_null());
1027        }
1028    }
1029
1030    #[test]
1031    fn test_string_char_at_out_of_bounds() {
1032        unsafe {
1033            let stack = std::ptr::null_mut();
1034            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1035            let stack = push(stack, Value::Int(10)); // Out of bounds
1036
1037            let stack = string_char_at(stack);
1038
1039            let (stack, result) = pop(stack);
1040            assert_eq!(result, Value::Int(-1));
1041            assert!(stack.is_null());
1042        }
1043    }
1044
1045    #[test]
1046    fn test_string_char_at_negative() {
1047        unsafe {
1048            let stack = std::ptr::null_mut();
1049            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1050            let stack = push(stack, Value::Int(-1));
1051
1052            let stack = string_char_at(stack);
1053
1054            let (stack, result) = pop(stack);
1055            assert_eq!(result, Value::Int(-1));
1056            assert!(stack.is_null());
1057        }
1058    }
1059
1060    #[test]
1061    fn test_string_substring_simple() {
1062        unsafe {
1063            let stack = std::ptr::null_mut();
1064            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1065            let stack = push(stack, Value::Int(1)); // start
1066            let stack = push(stack, Value::Int(3)); // len
1067
1068            let stack = string_substring(stack);
1069
1070            let (stack, result) = pop(stack);
1071            assert_eq!(result, Value::String(global_string("ell".to_owned())));
1072            assert!(stack.is_null());
1073        }
1074    }
1075
1076    #[test]
1077    fn test_string_substring_utf8() {
1078        // "héllo" - get "éll" (characters 1-3)
1079        unsafe {
1080            let stack = std::ptr::null_mut();
1081            let stack = push(stack, Value::String(global_string("héllo".to_owned())));
1082            let stack = push(stack, Value::Int(1)); // start
1083            let stack = push(stack, Value::Int(3)); // len
1084
1085            let stack = string_substring(stack);
1086
1087            let (stack, result) = pop(stack);
1088            assert_eq!(result, Value::String(global_string("éll".to_owned())));
1089            assert!(stack.is_null());
1090        }
1091    }
1092
1093    #[test]
1094    fn test_string_substring_clamp() {
1095        // Request more than available - should clamp
1096        unsafe {
1097            let stack = std::ptr::null_mut();
1098            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1099            let stack = push(stack, Value::Int(2)); // start
1100            let stack = push(stack, Value::Int(100)); // len (way too long)
1101
1102            let stack = string_substring(stack);
1103
1104            let (stack, result) = pop(stack);
1105            assert_eq!(result, Value::String(global_string("llo".to_owned())));
1106            assert!(stack.is_null());
1107        }
1108    }
1109
1110    #[test]
1111    fn test_string_substring_beyond_end() {
1112        // Start beyond end - returns empty
1113        unsafe {
1114            let stack = std::ptr::null_mut();
1115            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1116            let stack = push(stack, Value::Int(10)); // start (beyond end)
1117            let stack = push(stack, Value::Int(3)); // len
1118
1119            let stack = string_substring(stack);
1120
1121            let (stack, result) = pop(stack);
1122            assert_eq!(result, Value::String(global_string("".to_owned())));
1123            assert!(stack.is_null());
1124        }
1125    }
1126
1127    #[test]
1128    fn test_char_to_string_ascii() {
1129        unsafe {
1130            let stack = std::ptr::null_mut();
1131            let stack = push(stack, Value::Int(65)); // 'A'
1132
1133            let stack = char_to_string(stack);
1134
1135            let (stack, result) = pop(stack);
1136            assert_eq!(result, Value::String(global_string("A".to_owned())));
1137            assert!(stack.is_null());
1138        }
1139    }
1140
1141    #[test]
1142    fn test_char_to_string_utf8() {
1143        unsafe {
1144            let stack = std::ptr::null_mut();
1145            let stack = push(stack, Value::Int(233)); // 'é' = U+00E9
1146
1147            let stack = char_to_string(stack);
1148
1149            let (stack, result) = pop(stack);
1150            assert_eq!(result, Value::String(global_string("é".to_owned())));
1151            assert!(stack.is_null());
1152        }
1153    }
1154
1155    #[test]
1156    fn test_char_to_string_newline() {
1157        unsafe {
1158            let stack = std::ptr::null_mut();
1159            let stack = push(stack, Value::Int(10)); // '\n'
1160
1161            let stack = char_to_string(stack);
1162
1163            let (stack, result) = pop(stack);
1164            assert_eq!(result, Value::String(global_string("\n".to_owned())));
1165            assert!(stack.is_null());
1166        }
1167    }
1168
1169    #[test]
1170    fn test_char_to_string_invalid() {
1171        unsafe {
1172            let stack = std::ptr::null_mut();
1173            let stack = push(stack, Value::Int(-1)); // Invalid
1174
1175            let stack = char_to_string(stack);
1176
1177            let (stack, result) = pop(stack);
1178            assert_eq!(result, Value::String(global_string("".to_owned())));
1179            assert!(stack.is_null());
1180        }
1181    }
1182
1183    #[test]
1184    fn test_string_find_found() {
1185        unsafe {
1186            let stack = std::ptr::null_mut();
1187            let stack = push(
1188                stack,
1189                Value::String(global_string("hello world".to_owned())),
1190            );
1191            let stack = push(stack, Value::String(global_string("world".to_owned())));
1192
1193            let stack = string_find(stack);
1194
1195            let (stack, result) = pop(stack);
1196            assert_eq!(result, Value::Int(6)); // "world" starts at index 6
1197            assert!(stack.is_null());
1198        }
1199    }
1200
1201    #[test]
1202    fn test_string_find_not_found() {
1203        unsafe {
1204            let stack = std::ptr::null_mut();
1205            let stack = push(
1206                stack,
1207                Value::String(global_string("hello world".to_owned())),
1208            );
1209            let stack = push(stack, Value::String(global_string("xyz".to_owned())));
1210
1211            let stack = string_find(stack);
1212
1213            let (stack, result) = pop(stack);
1214            assert_eq!(result, Value::Int(-1));
1215            assert!(stack.is_null());
1216        }
1217    }
1218
1219    #[test]
1220    fn test_string_find_first_match() {
1221        // Should return first occurrence
1222        unsafe {
1223            let stack = std::ptr::null_mut();
1224            let stack = push(stack, Value::String(global_string("hello".to_owned())));
1225            let stack = push(stack, Value::String(global_string("l".to_owned())));
1226
1227            let stack = string_find(stack);
1228
1229            let (stack, result) = pop(stack);
1230            assert_eq!(result, Value::Int(2)); // First 'l' is at index 2
1231            assert!(stack.is_null());
1232        }
1233    }
1234
1235    #[test]
1236    fn test_string_find_utf8() {
1237        // Find in UTF-8 string - returns character index, not byte index
1238        unsafe {
1239            let stack = std::ptr::null_mut();
1240            let stack = push(
1241                stack,
1242                Value::String(global_string("héllo wörld".to_owned())),
1243            );
1244            let stack = push(stack, Value::String(global_string("wörld".to_owned())));
1245
1246            let stack = string_find(stack);
1247
1248            let (stack, result) = pop(stack);
1249            assert_eq!(result, Value::Int(6)); // Character index, not byte index
1250            assert!(stack.is_null());
1251        }
1252    }
1253
1254    // JSON Escape Tests
1255
1256    #[test]
1257    fn test_json_escape_quotes() {
1258        unsafe {
1259            let stack = std::ptr::null_mut();
1260            let stack = push(
1261                stack,
1262                Value::String(global_string("hello \"world\"".to_owned())),
1263            );
1264
1265            let stack = json_escape(stack);
1266
1267            let (stack, result) = pop(stack);
1268            assert_eq!(
1269                result,
1270                Value::String(global_string("hello \\\"world\\\"".to_owned()))
1271            );
1272            assert!(stack.is_null());
1273        }
1274    }
1275
1276    #[test]
1277    fn test_json_escape_backslash() {
1278        unsafe {
1279            let stack = std::ptr::null_mut();
1280            let stack = push(
1281                stack,
1282                Value::String(global_string("path\\to\\file".to_owned())),
1283            );
1284
1285            let stack = json_escape(stack);
1286
1287            let (stack, result) = pop(stack);
1288            assert_eq!(
1289                result,
1290                Value::String(global_string("path\\\\to\\\\file".to_owned()))
1291            );
1292            assert!(stack.is_null());
1293        }
1294    }
1295
1296    #[test]
1297    fn test_json_escape_newline_tab() {
1298        unsafe {
1299            let stack = std::ptr::null_mut();
1300            let stack = push(
1301                stack,
1302                Value::String(global_string("line1\nline2\ttabbed".to_owned())),
1303            );
1304
1305            let stack = json_escape(stack);
1306
1307            let (stack, result) = pop(stack);
1308            assert_eq!(
1309                result,
1310                Value::String(global_string("line1\\nline2\\ttabbed".to_owned()))
1311            );
1312            assert!(stack.is_null());
1313        }
1314    }
1315
1316    #[test]
1317    fn test_json_escape_carriage_return() {
1318        unsafe {
1319            let stack = std::ptr::null_mut();
1320            let stack = push(
1321                stack,
1322                Value::String(global_string("line1\r\nline2".to_owned())),
1323            );
1324
1325            let stack = json_escape(stack);
1326
1327            let (stack, result) = pop(stack);
1328            assert_eq!(
1329                result,
1330                Value::String(global_string("line1\\r\\nline2".to_owned()))
1331            );
1332            assert!(stack.is_null());
1333        }
1334    }
1335
1336    #[test]
1337    fn test_json_escape_control_chars() {
1338        unsafe {
1339            let stack = std::ptr::null_mut();
1340            // Test backspace (0x08) and form feed (0x0C)
1341            let stack = push(
1342                stack,
1343                Value::String(global_string("a\x08b\x0Cc".to_owned())),
1344            );
1345
1346            let stack = json_escape(stack);
1347
1348            let (stack, result) = pop(stack);
1349            assert_eq!(result, Value::String(global_string("a\\bb\\fc".to_owned())));
1350            assert!(stack.is_null());
1351        }
1352    }
1353
1354    #[test]
1355    fn test_json_escape_unicode_control() {
1356        unsafe {
1357            let stack = std::ptr::null_mut();
1358            // Test null character (0x00) - should be escaped as \u0000 (uppercase hex per RFC 8259)
1359            let stack = push(stack, Value::String(global_string("a\x00b".to_owned())));
1360
1361            let stack = json_escape(stack);
1362
1363            let (stack, result) = pop(stack);
1364            assert_eq!(result, Value::String(global_string("a\\u0000b".to_owned())));
1365            assert!(stack.is_null());
1366        }
1367    }
1368
1369    #[test]
1370    fn test_json_escape_mixed_special_chars() {
1371        // Test combination of multiple special characters
1372        unsafe {
1373            let stack = std::ptr::null_mut();
1374            let stack = push(
1375                stack,
1376                Value::String(global_string("Line 1\nLine \"2\"\ttab\r\n".to_owned())),
1377            );
1378
1379            let stack = json_escape(stack);
1380
1381            let (stack, result) = pop(stack);
1382            assert_eq!(
1383                result,
1384                Value::String(global_string(
1385                    "Line 1\\nLine \\\"2\\\"\\ttab\\r\\n".to_owned()
1386                ))
1387            );
1388            assert!(stack.is_null());
1389        }
1390    }
1391
1392    #[test]
1393    fn test_json_escape_no_change() {
1394        // Normal string without special chars should pass through unchanged
1395        unsafe {
1396            let stack = std::ptr::null_mut();
1397            let stack = push(
1398                stack,
1399                Value::String(global_string("Hello, World!".to_owned())),
1400            );
1401
1402            let stack = json_escape(stack);
1403
1404            let (stack, result) = pop(stack);
1405            assert_eq!(
1406                result,
1407                Value::String(global_string("Hello, World!".to_owned()))
1408            );
1409            assert!(stack.is_null());
1410        }
1411    }
1412
1413    #[test]
1414    fn test_json_escape_empty_string() {
1415        unsafe {
1416            let stack = std::ptr::null_mut();
1417            let stack = push(stack, Value::String(global_string("".to_owned())));
1418
1419            let stack = json_escape(stack);
1420
1421            let (stack, result) = pop(stack);
1422            assert_eq!(result, Value::String(global_string("".to_owned())));
1423            assert!(stack.is_null());
1424        }
1425    }
1426
1427    // string->int tests
1428
1429    #[test]
1430    fn test_string_to_int_success() {
1431        unsafe {
1432            let stack = std::ptr::null_mut();
1433            let stack = push(stack, Value::String(global_string("42".to_owned())));
1434
1435            let stack = string_to_int(stack);
1436
1437            let (stack, success) = pop(stack);
1438            let (stack, value) = pop(stack);
1439            assert_eq!(success, Value::Int(1));
1440            assert_eq!(value, Value::Int(42));
1441            assert!(stack.is_null());
1442        }
1443    }
1444
1445    #[test]
1446    fn test_string_to_int_negative() {
1447        unsafe {
1448            let stack = std::ptr::null_mut();
1449            let stack = push(stack, Value::String(global_string("-99".to_owned())));
1450
1451            let stack = string_to_int(stack);
1452
1453            let (stack, success) = pop(stack);
1454            let (stack, value) = pop(stack);
1455            assert_eq!(success, Value::Int(1));
1456            assert_eq!(value, Value::Int(-99));
1457            assert!(stack.is_null());
1458        }
1459    }
1460
1461    #[test]
1462    fn test_string_to_int_with_whitespace() {
1463        unsafe {
1464            let stack = std::ptr::null_mut();
1465            let stack = push(stack, Value::String(global_string("  123  ".to_owned())));
1466
1467            let stack = string_to_int(stack);
1468
1469            let (stack, success) = pop(stack);
1470            let (stack, value) = pop(stack);
1471            assert_eq!(success, Value::Int(1));
1472            assert_eq!(value, Value::Int(123));
1473            assert!(stack.is_null());
1474        }
1475    }
1476
1477    #[test]
1478    fn test_string_to_int_failure() {
1479        unsafe {
1480            let stack = std::ptr::null_mut();
1481            let stack = push(
1482                stack,
1483                Value::String(global_string("not a number".to_owned())),
1484            );
1485
1486            let stack = string_to_int(stack);
1487
1488            let (stack, success) = pop(stack);
1489            let (stack, value) = pop(stack);
1490            assert_eq!(success, Value::Int(0));
1491            assert_eq!(value, Value::Int(0));
1492            assert!(stack.is_null());
1493        }
1494    }
1495
1496    #[test]
1497    fn test_string_to_int_empty() {
1498        unsafe {
1499            let stack = std::ptr::null_mut();
1500            let stack = push(stack, Value::String(global_string("".to_owned())));
1501
1502            let stack = string_to_int(stack);
1503
1504            let (stack, success) = pop(stack);
1505            let (stack, value) = pop(stack);
1506            assert_eq!(success, Value::Int(0));
1507            assert_eq!(value, Value::Int(0));
1508            assert!(stack.is_null());
1509        }
1510    }
1511
1512    #[test]
1513    fn test_string_to_int_leading_zeros() {
1514        unsafe {
1515            let stack = std::ptr::null_mut();
1516            let stack = push(stack, Value::String(global_string("007".to_owned())));
1517
1518            let stack = string_to_int(stack);
1519
1520            let (stack, success) = pop(stack);
1521            let (stack, value) = pop(stack);
1522            assert_eq!(success, Value::Int(1));
1523            assert_eq!(value, Value::Int(7));
1524            assert!(stack.is_null());
1525        }
1526    }
1527}