runmat_runtime/builtins/strings/
common.rs

1//! Shared helpers for string builtins.
2use runmat_builtins::CharArray;
3
4/// Canonical display for missing string scalars in MATLAB-compatible output.
5const MISSING_SENTINEL: &str = "<missing>";
6
7/// Return `true` when the provided text represents a missing string scalar.
8#[inline]
9pub(crate) fn is_missing_string(text: &str) -> bool {
10    text.eq_ignore_ascii_case(MISSING_SENTINEL)
11}
12
13/// Convert text to lowercase while preserving MATLAB's `<missing>` sentinel.
14#[inline]
15pub(crate) fn lowercase_preserving_missing(text: String) -> String {
16    if is_missing_string(&text) {
17        MISSING_SENTINEL.to_string()
18    } else {
19        text.to_lowercase()
20    }
21}
22
23/// Convert text to uppercase while preserving MATLAB's `<missing>` sentinel.
24#[inline]
25pub(crate) fn uppercase_preserving_missing(text: String) -> String {
26    if is_missing_string(&text) {
27        MISSING_SENTINEL.to_string()
28    } else {
29        text.to_uppercase()
30    }
31}
32
33/// Collect a row from a [`CharArray`] into a `String`.
34#[inline]
35pub(crate) fn char_row_to_string(array: &CharArray, row: usize) -> String {
36    char_row_to_string_slice(&array.data, array.cols, row)
37}
38
39/// Collect a row from a character slice laid out in row-major order.
40#[inline]
41pub(crate) fn char_row_to_string_slice(data: &[char], cols: usize, row: usize) -> String {
42    let start = row * cols;
43    let end = start + cols;
44    data[start..end].iter().collect()
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn detects_missing_strings_case_insensitively() {
53        assert!(is_missing_string("<missing>"));
54        assert!(is_missing_string("<Missing>"));
55        assert!(!is_missing_string("<missing value>"));
56    }
57
58    #[test]
59    fn lowercase_preserves_missing() {
60        assert_eq!(
61            lowercase_preserving_missing("<missing>".to_string()),
62            "<missing>"
63        );
64        assert_eq!(lowercase_preserving_missing("RunMat".to_string()), "runmat");
65    }
66
67    #[test]
68    fn uppercase_preserves_missing() {
69        assert_eq!(
70            uppercase_preserving_missing("<missing>".to_string()),
71            "<missing>"
72        );
73        assert_eq!(uppercase_preserving_missing("RunMat".to_string()), "RUNMAT");
74    }
75
76    #[test]
77    fn char_row_collection_supports_row_major_storage() {
78        let chars: Vec<char> = vec!['A', 'B', 'C', 'D', 'E', 'F'];
79        assert_eq!(char_row_to_string_slice(&chars, 3, 0), "ABC");
80        assert_eq!(char_row_to_string_slice(&chars, 3, 1), "DEF");
81    }
82}