Skip to main content

stylex_css_parser/
base_types.rs

1/*!
2Base types and utilities for CSS parsing.
3
4This module provides fundamental types and utilities used throughout the CSS parser,
5including string manipulation utilities.
6*/
7
8use std::fmt::{self, Display};
9
10/// A string slice utility that provides efficient substring operations.
11#[derive(Debug, Clone)]
12pub struct SubString {
13  pub string: String,
14  pub start_index: usize,
15  pub end_index: usize,
16}
17
18impl SubString {
19  /// Create a new SubString from a string
20  pub fn new(s: &str) -> Self {
21    Self {
22      string: s.to_string(),
23      start_index: 0,
24      end_index: if s.is_empty() { 0 } else { s.len() - 1 },
25    }
26  }
27
28  /// Check if the substring starts with the given string
29  /// Uses a loop to avoid creating a new string
30  pub fn starts_with(&self, s: &str) -> bool {
31    let chars: Vec<char> = self.string.chars().collect();
32    let search_chars: Vec<char> = s.chars().collect();
33
34    for i in 0..search_chars.len() {
35      if self.start_index + i > self.end_index
36        || self.start_index + i >= chars.len()
37        || chars[self.start_index + i] != search_chars[i]
38      {
39        return false;
40      }
41    }
42    true
43  }
44
45  /// Get the first character of the substring
46  pub fn first(&self) -> Option<char> {
47    if self.start_index > self.end_index {
48      return None;
49    }
50    self.string.chars().nth(self.start_index)
51  }
52
53  /// Get a character at a relative index from the start
54  pub fn get(&self, relative_index: usize) -> Option<char> {
55    let absolute_index = self.start_index + relative_index;
56    if absolute_index > self.end_index {
57      return None;
58    }
59    self.string.chars().nth(absolute_index)
60  }
61
62  /// Convert the substring to a String
63  pub fn into_string(&self) -> String {
64    if self.start_index > self.end_index {
65      return String::new();
66    }
67
68    let chars: Vec<char> = self.string.chars().collect();
69    let start = self.start_index.min(chars.len());
70    let end = (self.end_index + 1).min(chars.len());
71
72    if start >= end {
73      String::new()
74    } else {
75      chars[start..end].iter().collect()
76    }
77  }
78
79  /// Check if the substring is empty
80  pub fn is_empty(&self) -> bool {
81    self.start_index > self.end_index
82  }
83}
84
85impl Display for SubString {
86  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87    write!(f, "{}", self.into_string())
88  }
89}
90
91#[cfg(test)]
92mod tests {
93  use super::*;
94
95  #[test]
96  fn test_new_with_string() {
97    let substr = SubString::new("hello");
98    assert_eq!(substr.string, "hello");
99    assert_eq!(substr.start_index, 0);
100    assert_eq!(substr.end_index, 4); // length - 1
101  }
102
103  #[test]
104  fn test_new_with_empty_string() {
105    let substr = SubString::new("");
106    assert_eq!(substr.string, "");
107    assert_eq!(substr.start_index, 0);
108    assert_eq!(substr.end_index, 0);
109  }
110
111  #[test]
112  fn test_starts_with() {
113    let substr = SubString::new("hello world");
114    assert!(substr.starts_with("hello"));
115    assert!(substr.starts_with("h"));
116    assert!(substr.starts_with(""));
117    assert!(!substr.starts_with("world"));
118    assert!(!substr.starts_with("hello world!")); // longer than original
119  }
120
121  #[test]
122  fn test_first() {
123    let substr = SubString::new("hello");
124    assert_eq!(substr.first(), Some('h'));
125
126    let empty_substr = SubString::new("");
127    assert_eq!(empty_substr.first(), None);
128  }
129
130  #[test]
131  fn test_get() {
132    let substr = SubString::new("hello");
133    assert_eq!(substr.get(0), Some('h'));
134    assert_eq!(substr.get(1), Some('e'));
135    assert_eq!(substr.get(4), Some('o'));
136    assert_eq!(substr.get(5), None); // out of bounds
137  }
138
139  #[test]
140  fn test_to_string() {
141    let substr = SubString::new("hello");
142    assert_eq!(substr.to_string(), "hello");
143
144    let empty_substr = SubString::new("");
145    assert_eq!(empty_substr.to_string(), "");
146  }
147
148  #[test]
149  fn test_is_empty() {
150    let substr = SubString::new("hello");
151    assert!(!substr.is_empty());
152
153    let empty_substr = SubString::new("");
154    assert!(!empty_substr.is_empty()); // Even empty string has start_index=0, end_index=0
155
156    // Test with manually modified indices
157    let mut modified_substr = SubString::new("hello");
158    modified_substr.start_index = 3;
159    modified_substr.end_index = 2; // start_index > end_index
160    assert!(modified_substr.is_empty());
161  }
162
163  #[test]
164  fn test_unicode_support() {
165    let substr = SubString::new("héllo 🌍");
166    assert_eq!(substr.first(), Some('h'));
167    assert_eq!(substr.get(1), Some('é'));
168    assert!(substr.starts_with("hé"));
169    assert_eq!(substr.to_string(), "héllo 🌍");
170  }
171}