abi_definitions/
string.rs

1use core::{
2    cmp::{Ordering, min},
3    ffi::{c_char, c_int},
4    ptr::null_mut,
5    slice,
6};
7
8use alloc::str;
9
10use crate::memory::xila_memory_allocate_core;
11
12/// Helper function to convert C string to Rust str for parsing
13unsafe fn c_str_to_str(ptr: *const c_char) -> Result<&'static str, ()> {
14    unsafe {
15        if ptr.is_null() {
16            return Err(());
17        }
18
19        let len = xila_string_get_length(ptr);
20        let slice = slice::from_raw_parts(ptr as *const u8, len);
21        core::str::from_utf8(slice).map_err(|_| ())
22    }
23}
24
25/// Get the length of a null-terminated string
26///
27/// # Safety
28/// This function is unsafe because it dereferences raw pointers.
29#[unsafe(no_mangle)]
30pub unsafe extern "C" fn xila_string_get_length(str: *const c_char) -> usize {
31    unsafe {
32        if str.is_null() {
33            return 0;
34        }
35
36        let mut len = 0;
37        let mut ptr = str;
38        while *ptr != 0 {
39            len += 1;
40            ptr = ptr.add(1);
41        }
42        len
43    }
44}
45
46/// Get the length of a null-terminated string with maximum length
47///
48/// # Safety
49/// This function is unsafe because it dereferences raw pointers.
50#[unsafe(no_mangle)]
51pub unsafe extern "C" fn xila_string_get_length_bounded(
52    str: *const c_char,
53    maxlen: usize,
54) -> usize {
55    unsafe {
56        if str.is_null() {
57            return 0;
58        }
59
60        let mut len = 0;
61        let mut ptr = str;
62        while len < maxlen && *ptr != 0 {
63            len += 1;
64            ptr = ptr.add(1);
65        }
66        len
67    }
68}
69
70/// Compare two null-terminated strings
71///
72/// # Safety
73/// This function is unsafe because it dereferences raw pointers.
74#[unsafe(no_mangle)]
75pub unsafe extern "C" fn xila_string_compare(str1: *const c_char, str2: *const c_char) -> c_int {
76    unsafe {
77        if str1.is_null() || str2.is_null() {
78            return if str1.is_null() && str2.is_null() {
79                0
80            } else if str1.is_null() {
81                -1
82            } else {
83                1
84            };
85        }
86
87        let len1 = xila_string_get_length(str1);
88        let len2 = xila_string_get_length(str2);
89
90        // Use Rust's slice comparison for efficiency
91        let slice1 = slice::from_raw_parts(str1 as *const u8, len1);
92        let slice2 = slice::from_raw_parts(str2 as *const u8, len2);
93
94        match slice1.cmp(slice2) {
95            Ordering::Less => -1,
96            Ordering::Equal => 0,
97            Ordering::Greater => 1,
98        }
99    }
100}
101
102/// Compare two strings up to n characters
103///
104/// # Safety
105/// This function is unsafe because it dereferences raw pointers.
106#[unsafe(no_mangle)]
107pub unsafe extern "C" fn xila_string_compare_bounded(
108    str1: *const c_char,
109    str2: *const c_char,
110    num: usize,
111) -> c_int {
112    unsafe {
113        if str1.is_null() || str2.is_null() || num == 0 {
114            return if str1.is_null() && str2.is_null() {
115                0
116            } else if str1.is_null() {
117                -1
118            } else {
119                1
120            };
121        }
122
123        let len1 = min(xila_string_get_length(str1), num);
124        let len2 = min(xila_string_get_length(str2), num);
125        let min_len = min(len1, len2);
126
127        // Use Rust's slice comparison for the overlapping part
128        let slice1 = slice::from_raw_parts(str1 as *const u8, min_len);
129        let slice2 = slice::from_raw_parts(str2 as *const u8, min_len);
130
131        use core::cmp::Ordering;
132        match slice1.cmp(slice2) {
133            Ordering::Less => -1,
134            Ordering::Greater => 1,
135            Ordering::Equal => {
136                // If the compared parts are equal, compare lengths
137                match len1.cmp(&len2) {
138                    Ordering::Less => -1,
139                    Ordering::Equal => 0,
140                    Ordering::Greater => 1,
141                }
142            }
143        }
144    }
145}
146
147/// Copy string from source to destination
148///
149/// # Safety
150/// This function is unsafe because it dereferences raw pointers.
151#[unsafe(no_mangle)]
152pub unsafe extern "C" fn xila_string_copy(
153    destination: *mut c_char,
154    source: *const c_char,
155) -> *mut c_char {
156    if destination.is_null() || source.is_null() {
157        return destination;
158    }
159
160    let mut dst_ptr = destination;
161    let mut src_ptr = source;
162
163    unsafe {
164        // Copy characters until null terminator
165        while *src_ptr != 0 {
166            *dst_ptr = *src_ptr;
167            dst_ptr = dst_ptr.add(1);
168            src_ptr = src_ptr.add(1);
169        }
170
171        // Null-terminate the destination string
172        *dst_ptr = 0;
173    }
174
175    destination
176}
177
178/// Copy string from source to destination with maximum length
179///
180/// # Safety
181/// This function is unsafe because it dereferences raw pointers.
182#[unsafe(no_mangle)]
183pub unsafe extern "C" fn xila_string_copy_bounded(
184    destination: *mut c_char,
185    source: *const c_char,
186    num: usize,
187) -> *mut c_char {
188    unsafe {
189        if destination.is_null() || source.is_null() || num == 0 {
190            return destination;
191        }
192
193        let mut dst_ptr = destination;
194        let mut src_ptr = source;
195        let mut count = 0;
196
197        // Copy characters until null terminator or num reached
198        while count < num && *src_ptr != 0 {
199            *dst_ptr = *src_ptr;
200            dst_ptr = dst_ptr.add(1);
201            src_ptr = src_ptr.add(1);
202            count += 1;
203        }
204
205        // Pad with null bytes if necessary
206        while count < num {
207            *dst_ptr = 0;
208            dst_ptr = dst_ptr.add(1);
209            count += 1;
210        }
211
212        destination
213    }
214}
215
216/// Tokenize a string using delimiters
217///
218/// # Safety
219/// This function is unsafe because it dereferences raw pointers.
220#[unsafe(no_mangle)]
221pub unsafe extern "C" fn xila_string_tokenize(
222    _string: *mut c_char,
223    delimiters: *const c_char,
224) -> *mut c_char {
225    // Note: This is a simplified implementation
226    // A full strtok implementation requires static state management
227    if delimiters.is_null() {
228        return null_mut();
229    }
230
231    // This is a basic implementation that doesn't maintain state
232    // In a real implementation, you'd need to track the current position
233    null_mut()
234}
235
236/// Find substring in a string
237///
238/// # Safety
239/// This function is unsafe because it dereferences raw pointers.
240#[unsafe(no_mangle)]
241pub unsafe extern "C" fn xila_string_find_substring(
242    haystack: *const c_char,
243    needle: *const c_char,
244) -> *mut c_char {
245    unsafe {
246        if haystack.is_null() || needle.is_null() {
247            return null_mut();
248        }
249
250        let needle_len = xila_string_get_length(needle);
251        if needle_len == 0 {
252            return haystack as *mut c_char;
253        }
254
255        let haystack_len = xila_string_get_length(haystack);
256        if needle_len > haystack_len {
257            return null_mut();
258        }
259
260        // Use slice operations for efficient searching
261        let haystack_slice = slice::from_raw_parts(haystack as *const u8, haystack_len);
262        let needle_slice = slice::from_raw_parts(needle as *const u8, needle_len);
263
264        // Use Rust's windows iterator for efficient substring search
265        for (i, window) in haystack_slice.windows(needle_len).enumerate() {
266            if window == needle_slice {
267                return haystack.add(i) as *mut c_char;
268            }
269        }
270
271        null_mut()
272    }
273}
274
275/// Convert string to double
276///
277/// # Safety
278/// This function is unsafe because it dereferences raw pointers.
279#[unsafe(no_mangle)]
280pub unsafe extern "C" fn xila_string_to_double(
281    nptr: *const c_char,
282    endptr: *mut *mut c_char,
283) -> f64 {
284    unsafe {
285        if nptr.is_null() {
286            if !endptr.is_null() {
287                *endptr = nptr as *mut c_char;
288            }
289            return 0.0;
290        }
291
292        // Try to parse using Rust's str parsing
293        if let Ok(s) = c_str_to_str(nptr) {
294            let trimmed = s.trim_start();
295            if let Ok(value) = trimmed.parse::<f64>() {
296                if !endptr.is_null() {
297                    // Calculate how many characters were consumed
298                    let consumed = s.len() - trimmed.len()
299                        + trimmed.chars().take_while(|c| !c.is_whitespace()).count();
300                    *endptr = nptr.add(consumed) as *mut c_char;
301                }
302                return value;
303            }
304        }
305
306        if !endptr.is_null() {
307            *endptr = nptr as *mut c_char;
308        }
309        0.0
310    }
311}
312
313/// Case-insensitive string comparison up to n characters
314///
315/// # Safety
316/// This function is unsafe because it dereferences raw pointers.
317#[unsafe(no_mangle)]
318pub unsafe extern "C" fn xila_string_compare_case_insensitive_bounded(
319    str1: *const c_char,
320    str2: *const c_char,
321    num: usize,
322) -> c_int {
323    unsafe {
324        if str1.is_null() || str2.is_null() || num == 0 {
325            return 0;
326        }
327
328        let len1 = min(xila_string_get_length(str1), num);
329        let len2 = min(xila_string_get_length(str2), num);
330        let min_len = min(len1, len2);
331
332        // Convert both slices to lowercase and compare
333        let slice1 = slice::from_raw_parts(str1 as *const u8, min_len);
334        let slice2 = slice::from_raw_parts(str2 as *const u8, min_len);
335
336        for (a, b) in slice1.iter().zip(slice2.iter()) {
337            let lower_a = a.to_ascii_lowercase();
338            let lower_b = b.to_ascii_lowercase();
339            if lower_a != lower_b {
340                return if lower_a < lower_b { -1 } else { 1 };
341            }
342        }
343
344        // If compared parts are equal, compare lengths
345        use core::cmp::Ordering;
346        match len1.cmp(&len2) {
347            Ordering::Less => -1,
348            Ordering::Equal => 0,
349            Ordering::Greater => 1,
350        }
351    }
352}
353
354/// Convert string to unsigned long
355///
356/// # Safety
357/// This function is unsafe because it dereferences raw pointers.
358#[unsafe(no_mangle)]
359pub unsafe extern "C" fn xila_string_to_unsigned_long(
360    nptr: *const c_char,
361    endptr: *mut *mut c_char,
362    base: c_int,
363) -> u64 {
364    unsafe {
365        if nptr.is_null() {
366            if !endptr.is_null() {
367                *endptr = nptr as *mut c_char;
368            }
369            return 0;
370        }
371
372        // Try to parse using Rust's str parsing
373        if let Ok(s) = c_str_to_str(nptr) {
374            let trimmed = s.trim_start();
375            let radix = if base == 0 {
376                // Auto-detect base like C strtoul
377                if trimmed.starts_with("0x") || trimmed.starts_with("0X") {
378                    16
379                } else if trimmed.starts_with("0") && trimmed.len() > 1 {
380                    8
381                } else {
382                    10
383                }
384            } else {
385                base as u32
386            };
387
388            if (2..=36).contains(&radix) {
389                let parse_str =
390                    if radix == 16 && (trimmed.starts_with("0x") || trimmed.starts_with("0X")) {
391                        &trimmed[2..]
392                    } else {
393                        trimmed
394                    };
395
396                if let Ok(value) = u64::from_str_radix(parse_str, radix) {
397                    if !endptr.is_null() {
398                        let consumed = s.len() - trimmed.len()
399                            + if radix == 16 && parse_str != trimmed {
400                                2
401                            } else {
402                                0
403                            }
404                            + parse_str
405                                .chars()
406                                .take_while(|c| c.is_ascii_alphanumeric())
407                                .count();
408                        *endptr = nptr.add(consumed) as *mut c_char;
409                    }
410                    return value;
411                }
412            }
413        }
414
415        if !endptr.is_null() {
416            *endptr = nptr as *mut c_char;
417        }
418        0
419    }
420}
421
422/// Find character in string
423///
424/// # Safety
425/// This function is unsafe because it dereferences raw pointers.
426#[unsafe(no_mangle)]
427pub unsafe extern "C" fn xila_string_find_character(s: *const c_char, c: c_int) -> *mut c_char {
428    unsafe {
429        if s.is_null() {
430            return null_mut();
431        }
432
433        let target = c as u8;
434        let len = xila_string_get_length(s);
435        let slice = slice::from_raw_parts(s as *const u8, len + 1); // +1 to include null terminator
436
437        // Use Rust's efficient position finding
438        if let Some(pos) = slice.iter().position(|&byte| byte == target) {
439            return s.add(pos) as *mut c_char;
440        }
441
442        null_mut()
443    }
444}
445
446/// Convert string to float
447///
448/// # Safety
449/// This function is unsafe because it dereferences raw pointers.
450#[unsafe(no_mangle)]
451pub unsafe extern "C" fn xila_string_to_float(
452    nptr: *const c_char,
453    endptr: *mut *mut c_char,
454) -> f32 {
455    unsafe {
456        if nptr.is_null() {
457            if !endptr.is_null() {
458                *endptr = nptr as *mut c_char;
459            }
460            return 0.0;
461        }
462
463        // Try to parse using Rust's str parsing
464        if let Ok(s) = c_str_to_str(nptr) {
465            let trimmed = s.trim_start();
466            if let Ok(value) = trimmed.parse::<f32>() {
467                if !endptr.is_null() {
468                    // Calculate how many characters were consumed
469                    let consumed = s.len() - trimmed.len()
470                        + trimmed.chars().take_while(|c| !c.is_whitespace()).count();
471                    *endptr = nptr.add(consumed) as *mut c_char;
472                }
473                return value;
474            }
475        }
476
477        if !endptr.is_null() {
478            *endptr = nptr as *mut c_char;
479        }
480        0.0
481    }
482}
483
484/// Get length of initial segment of string that consists of reject characters
485///
486/// # Safety
487/// This function is unsafe because it dereferences raw pointers.
488#[unsafe(no_mangle)]
489pub unsafe extern "C" fn xila_string_span_complement(
490    s: *const c_char,
491    reject: *const c_char,
492) -> usize {
493    unsafe {
494        if s.is_null() || reject.is_null() {
495            return 0;
496        }
497
498        let s_len = xila_string_get_length(s);
499        let reject_len = xila_string_get_length(reject);
500
501        let s_slice = slice::from_raw_parts(s as *const u8, s_len);
502        let reject_slice = slice::from_raw_parts(reject as *const u8, reject_len);
503
504        // Use Rust's iterator methods for efficient searching
505        s_slice
506            .iter()
507            .position(|&byte| reject_slice.contains(&byte))
508            .unwrap_or(s_len)
509    }
510}
511
512/// Get length of initial segment of string that consists of accept characters
513///
514/// # Safety
515/// This function is unsafe because it dereferences raw pointers.
516#[unsafe(no_mangle)]
517pub unsafe extern "C" fn xila_string_span(s: *const c_char, accept: *const c_char) -> usize {
518    unsafe {
519        if s.is_null() || accept.is_null() {
520            return 0;
521        }
522
523        let s_len = xila_string_get_length(s);
524        let accept_len = xila_string_get_length(accept);
525
526        let s_slice = slice::from_raw_parts(s as *const u8, s_len);
527        let accept_slice = slice::from_raw_parts(accept as *const u8, accept_len);
528
529        // Use Rust's iterator methods for efficient searching
530        s_slice
531            .iter()
532            .position(|&byte| !accept_slice.contains(&byte))
533            .unwrap_or(s_len)
534    }
535}
536
537/// Convert string to unsigned long long
538///
539/// # Safety
540/// This function is unsafe because it dereferences raw pointers.
541#[unsafe(no_mangle)]
542pub unsafe extern "C" fn xila_string_to_unsigned_long_long(
543    nptr: *const c_char,
544    endptr: *mut *mut c_char,
545    base: c_int,
546) -> u64 {
547    unsafe {
548        // strtoull and strtoul have the same implementation for u64
549        xila_string_to_unsigned_long(nptr, endptr, base)
550    }
551}
552
553/// Duplicate a string
554///
555/// # Safety
556/// This function is unsafe because it dereferences raw pointers.
557#[unsafe(no_mangle)]
558pub unsafe extern "C" fn xila_string_duplicate(string: *const c_char) -> *mut c_char {
559    if string.is_null() {
560        return null_mut();
561    }
562
563    let new_string = unsafe {
564        let length = xila_string_get_length(string);
565        xila_memory_allocate_core(length + 1)
566    };
567
568    if new_string.is_null() {
569        return null_mut();
570    }
571
572    // Copy the string into the newly allocated memory
573    unsafe {
574        xila_string_copy(new_string as *mut c_char, string);
575    }
576
577    new_string as *mut c_char
578}
579
580/// Duplicate a string up to a maximum length
581///
582/// # Safety
583/// This function is unsafe because it dereferences raw pointers.
584#[unsafe(no_mangle)]
585pub unsafe extern "C" fn xila_string_duplicate_bounded(
586    string: *const c_char,
587    max_length: usize,
588) -> *mut c_char {
589    if string.is_null() || max_length == 0 {
590        return null_mut();
591    }
592
593    let length = unsafe { xila_string_get_length_bounded(string, max_length) };
594    let new_string = unsafe { xila_memory_allocate_core(length + 1) };
595
596    if new_string.is_null() {
597        return null_mut();
598    }
599
600    // Copy the string into the newly allocated memory
601    unsafe {
602        xila_string_copy_bounded(new_string as *mut c_char, string, length);
603    }
604
605    new_string as *mut c_char
606}
607
608/// Parse integer from string
609///
610/// # Safety
611/// This function is unsafe because it dereferences raw pointers.
612#[unsafe(no_mangle)]
613pub unsafe extern "C" fn xila_string_parse_integer(string: *const c_char) -> c_int {
614    unsafe {
615        if string.is_null() {
616            return 0;
617        }
618
619        // Convert C string to Rust str
620        if let Ok(s) = c_str_to_str(string) {
621            // Parse the string as an integer
622            if let Ok(value) = s.trim().parse::<c_int>() {
623                return value;
624            }
625        }
626
627        0 // Return 0 if parsing fails
628    }
629}
630
631/// Concatenate source string to destination string
632///
633/// # Safety
634/// This function is unsafe because it dereferences raw pointers.
635#[unsafe(no_mangle)]
636pub unsafe extern "C" fn xila_string_concatenate(
637    destination: *mut c_char,
638    mut source: *const c_char,
639) -> *mut c_char {
640    if destination.is_null() || source.is_null() {
641        return destination;
642    }
643
644    // Find the end of dest string
645    let mut destination_end = destination;
646    unsafe {
647        while *destination_end != 0 {
648            destination_end = destination_end.add(1);
649        }
650    }
651
652    unsafe {
653        // Copy src to the end of dest (including null terminator)
654        while *source != 0 {
655            *destination_end = *source;
656            destination_end = destination_end.add(1);
657            source = source.add(1);
658        }
659        // Add null terminator
660        *destination_end = 0;
661    }
662
663    destination
664}