Skip to main content

seq_runtime/string_ops/
basic.rs

1//! Basic string operations: length, byte-length, empty-check, equality, concat.
2
3use crate::seqstring::global_bytes;
4use crate::stack::{Stack, pop, push};
5use crate::value::Value;
6
7/// # Safety
8/// Stack must have the expected values on top for this operation.
9#[unsafe(no_mangle)]
10pub unsafe extern "C" fn patch_seq_string_empty(stack: Stack) -> Stack {
11    assert!(!stack.is_null(), "string_empty: stack is empty");
12
13    let (stack, value) = unsafe { pop(stack) };
14
15    match value {
16        Value::String(s) => {
17            // Byte-clean: empty iff there are no bytes, regardless of UTF-8.
18            let is_empty = s.as_bytes().is_empty();
19            unsafe { push(stack, Value::Bool(is_empty)) }
20        }
21        _ => panic!("string_empty: expected String on stack"),
22    }
23}
24
25/// Check if a string contains a substring
26///
27/// Stack effect: ( str substring -- bool )
28///
29/// # Safety
30/// Stack must have two String values on top
31#[unsafe(no_mangle)]
32pub unsafe extern "C" fn patch_seq_string_concat(stack: Stack) -> Stack {
33    assert!(!stack.is_null(), "string_concat: stack is empty");
34
35    let (stack, str2_val) = unsafe { pop(stack) };
36    assert!(!stack.is_null(), "string_concat: need two strings");
37    let (stack, str1_val) = unsafe { pop(stack) };
38
39    match (str1_val, str2_val) {
40        (Value::String(s1), Value::String(s2)) => {
41            // Byte-clean concat: catenate the underlying byte buffers.
42            // OSC payload assembly, binary file building, MessagePack,
43            // anything that needs to glue arbitrary bytes together
44            // depends on this not going through `&str`.
45            let mut result = Vec::with_capacity(s1.as_bytes().len() + s2.as_bytes().len());
46            result.extend_from_slice(s1.as_bytes());
47            result.extend_from_slice(s2.as_bytes());
48            unsafe { push(stack, Value::String(global_bytes(result))) }
49        }
50        _ => panic!("string_concat: expected two strings on stack"),
51    }
52}
53
54/// Get the length of a string in Unicode characters (code points)
55///
56/// Stack effect: ( str -- int )
57///
58/// Note: This returns character count, not byte count.
59/// For UTF-8 byte length (e.g., HTTP Content-Length), use `string-byte-length`.
60///
61/// # Safety
62/// Stack must have a String value on top
63#[unsafe(no_mangle)]
64pub unsafe extern "C" fn patch_seq_string_length(stack: Stack) -> Stack {
65    assert!(!stack.is_null(), "string_length: stack is empty");
66
67    let (stack, str_val) = unsafe { pop(stack) };
68
69    match str_val {
70        Value::String(s) => {
71            let len = s.as_str_or_empty().chars().count() as i64;
72            unsafe { push(stack, Value::Int(len)) }
73        }
74        _ => panic!("string_length: expected String on stack"),
75    }
76}
77
78/// Get the byte length of a string (UTF-8 encoded)
79///
80/// Stack effect: ( str -- int )
81///
82/// Use this for HTTP Content-Length headers and buffer allocation.
83///
84/// # Safety
85/// Stack must have a String value on top
86#[unsafe(no_mangle)]
87pub unsafe extern "C" fn patch_seq_string_byte_length(stack: Stack) -> Stack {
88    assert!(!stack.is_null(), "string_byte_length: stack is empty");
89
90    let (stack, str_val) = unsafe { pop(stack) };
91
92    match str_val {
93        Value::String(s) => {
94            // Byte-clean: count of underlying bytes, no UTF-8 validation.
95            let len = s.as_bytes().len() as i64;
96            unsafe { push(stack, Value::Int(len)) }
97        }
98        _ => panic!("string_byte_length: expected String on stack"),
99    }
100}
101
102/// Get the Unicode code point at a character index
103///
104/// Stack effect: ( str int -- int )
105///
106/// Returns the code point value at the given character index.
107/// Returns -1 if index is out of bounds.
108///
109/// # Safety
110/// Stack must have a String and Int on top
111#[unsafe(no_mangle)]
112pub unsafe extern "C" fn patch_seq_string_equal(stack: Stack) -> Stack {
113    assert!(!stack.is_null(), "string_equal: stack is empty");
114
115    let (stack, str2_val) = unsafe { pop(stack) };
116    assert!(!stack.is_null(), "string_equal: need two strings");
117    let (stack, str1_val) = unsafe { pop(stack) };
118
119    match (str1_val, str2_val) {
120        (Value::String(s1), Value::String(s2)) => {
121            // Byte-clean: equality is byte-for-byte. Matches the
122            // `PartialEq for SeqString` impl in seqstring.rs, which
123            // also compares `as_bytes`.
124            let equal = s1.as_bytes() == s2.as_bytes();
125            unsafe { push(stack, Value::Bool(equal)) }
126        }
127        _ => panic!("string_equal: expected two strings on stack"),
128    }
129}