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