spinoso_regexp/state.rs
1/// Container for Ruby VM-level Regexp engine state.
2///
3/// Using [`Regexp`]s in Ruby code can set [regexp global variables]:
4///
5/// - `$~` is equivalent to `Regexp.last_match`.
6/// - `$&` contains the complete matched text.
7/// - `` $` `` contains string before match.
8/// - `$'` contains string after match.
9/// - `$1`, `$2` and so on contain text matching first, second, etc capture group.
10/// - `$+` contains last capture group.
11///
12/// This struct is used by the implementation of `Regexp` in Artichoke's Ruby
13/// Core to track this global state.
14///
15/// [`Regexp`]: https://ruby-doc.org/3.2.2/Regexp.html
16/// [regexp global variables]: https://ruby-doc.org/3.2.2/Regexp.html#class-Regexp-label-Regexp+Global+Variables
17#[allow(missing_copy_implementations)] // this is a mutable container
18#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
19pub struct State {
20 capture_group_globals: usize,
21}
22
23impl State {
24 /// Constructs a new, empty `Regexp` state.
25 ///
26 /// # Examples
27 ///
28 /// ```
29 /// use spinoso_regexp::State;
30 /// let state = State::new();
31 /// ```
32 #[inline]
33 #[must_use]
34 pub const fn new() -> Self {
35 Self {
36 capture_group_globals: 0,
37 }
38 }
39
40 /// Reset the state to empty.
41 ///
42 /// # Examples
43 ///
44 /// ```
45 /// use spinoso_regexp::State;
46 /// let mut state = State::new();
47 /// state.clear();
48 /// ```
49 #[inline]
50 pub fn clear(&mut self) {
51 *self = Self::new();
52 }
53
54 /// Retrieve the count of currently active `Regexp` capture group globals.
55 ///
56 /// This count is used to track how many `$1`, `$2`, etc. variables are set
57 /// to non-nil values.
58 ///
59 /// # Examples
60 ///
61 /// ```
62 /// use spinoso_regexp::State;
63 ///
64 /// let mut state = State::new();
65 /// assert_eq!(state.capture_group_globals(), 0);
66 ///
67 /// state.set_capture_group_globals(7);
68 /// assert_eq!(state.capture_group_globals(), 7);
69 /// ```
70 #[inline]
71 #[must_use]
72 pub const fn capture_group_globals(&self) -> usize {
73 self.capture_group_globals
74 }
75
76 /// Set the count of currently active `Regexp` capture group globals.
77 ///
78 /// This count is used to track how many `$1`, `$2`, etc. variables are set
79 /// to non-nil values.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use spinoso_regexp::State;
85 ///
86 /// let mut state = State::new();
87 /// assert_eq!(state.capture_group_globals(), 0);
88 ///
89 /// state.set_capture_group_globals(7);
90 /// assert_eq!(state.capture_group_globals(), 7);
91 /// ```
92 #[inline]
93 pub fn set_capture_group_globals(&mut self, count: usize) {
94 self.capture_group_globals = count;
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::fmt::Write;
101
102 use super::State;
103
104 #[test]
105 fn new_is_const() {
106 const _: State = State::new();
107 }
108
109 #[test]
110 fn new_is_empty() {
111 let state = State::new();
112 assert_eq!(state.capture_group_globals(), 0);
113 }
114
115 #[test]
116 fn default_is_empty() {
117 let state = State::default();
118 assert_eq!(state.capture_group_globals(), 0);
119 }
120
121 #[test]
122 fn clear_sets_capture_group_globals_to_zero() {
123 let mut state = State::new();
124 state.clear();
125 assert_eq!(state.capture_group_globals(), 0);
126
127 state.set_capture_group_globals(9);
128 state.clear();
129 assert_eq!(state.capture_group_globals(), 0);
130 }
131
132 #[test]
133 fn set_get_capture_group_globals() {
134 let test_cases = [
135 0,
136 1,
137 2,
138 3,
139 4,
140 6,
141 6,
142 7,
143 8,
144 9,
145 10,
146 99,
147 1024,
148 usize::try_from(i32::MAX).unwrap(),
149 usize::MAX,
150 ];
151 for count in test_cases {
152 let mut state = State::new();
153 state.set_capture_group_globals(count);
154 assert_eq!(state.capture_group_globals(), count);
155 }
156 }
157
158 #[test]
159 fn debug_is_not_empty() {
160 let state = State::new();
161 let mut s = String::new();
162 write!(&mut s, "{state:?}").unwrap();
163 assert!(!s.is_empty());
164 }
165
166 #[test]
167 fn clone_preserves_state() {
168 let mut state = State::new();
169 assert_eq!(state.capture_group_globals(), state.clone().capture_group_globals());
170
171 state.set_capture_group_globals(29);
172 assert_eq!(state.capture_group_globals(), state.clone().capture_group_globals());
173 }
174
175 #[test]
176 fn partial_eq_is_reflexive() {
177 let mut state = State::new();
178 assert_eq!(&state, &state);
179
180 state.set_capture_group_globals(17);
181 assert_eq!(&state, &state);
182 }
183
184 #[test]
185 fn partial_eq_not_eq() {
186 let mut left = State::new();
187 left.set_capture_group_globals(56);
188
189 let mut right = State::new();
190 right.set_capture_group_globals(35);
191
192 assert_ne!(left, right);
193 assert_ne!(right, left);
194 }
195}