1use std::{
4 ops::{ControlFlow, Range},
5 sync::Arc,
6};
7
8use javelin_sys as sys;
9
10use crate::{Error, ffi::Code};
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct Match<'s> {
15 subject: &'s [u8],
16 start: usize,
17 end: usize,
18}
19
20impl<'s> Match<'s> {
21 pub(crate) fn new(subject: &'s [u8], start: usize, end: usize) -> Self {
22 assert!(
23 start <= end && end <= subject.len(),
24 "invalid native match range"
25 );
26 Self {
27 subject,
28 start,
29 end,
30 }
31 }
32
33 pub fn start(&self) -> usize {
35 self.start
36 }
37
38 pub fn end(&self) -> usize {
40 self.end
41 }
42
43 pub fn range(&self) -> Range<usize> {
45 self.start..self.end
46 }
47
48 pub fn as_bytes(&self) -> &'s [u8] {
50 &self.subject[self.start..self.end]
51 }
52}
53
54#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct Captures<'s> {
60 pub(crate) subject: &'s [u8],
61 pub(crate) ranges: Vec<Option<(usize, usize)>>,
62}
63
64impl<'s> Captures<'s> {
65 pub fn get(&self, index: usize) -> Option<Match<'s>> {
67 let (start, end) = self.ranges.get(index).copied().flatten()?;
68 Some(Match::new(self.subject, start, end))
69 }
70
71 pub fn len(&self) -> usize {
73 self.ranges.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.ranges.is_empty()
79 }
80
81 pub fn iter(&self) -> impl ExactSizeIterator<Item = Option<Match<'s>>> + '_ {
83 (0..self.len()).map(|index| self.get(index))
84 }
85}
86
87#[derive(Clone, Debug, Default)]
90pub struct RegexBuilder {
91 options: i32,
92}
93
94impl RegexBuilder {
95 pub fn new() -> Self {
97 Self::default()
98 }
99
100 pub fn build(&self, pattern: &str) -> Result<Regex, Error> {
102 Ok(Regex {
103 code: Arc::new(Code::new(pattern, self.options)?),
104 pattern: Arc::from(pattern),
105 })
106 }
107
108 fn flag(&mut self, flag: i32, enabled: bool) -> &mut Self {
109 if enabled {
110 self.options |= flag;
111 } else {
112 self.options &= !flag;
113 }
114 self
115 }
116
117 pub fn caseless(&mut self, yes: bool) -> &mut Self {
119 self.flag(sys::JP_OPTION_IGNORE_CASE, yes)
120 }
121
122 pub fn multi_line(&mut self, yes: bool) -> &mut Self {
124 self.flag(sys::JP_OPTION_MULTILINE, yes)
125 }
126
127 pub fn dotall(&mut self, yes: bool) -> &mut Self {
129 self.flag(sys::JP_OPTION_DOTALL, yes)
130 }
131
132 pub fn utf(&mut self, yes: bool) -> &mut Self {
134 self.flag(sys::JP_OPTION_UTF8, yes)
135 }
136
137 pub fn ucp(&mut self, yes: bool) -> &mut Self {
140 self.flag(sys::JP_OPTION_UCP, yes)
141 }
142
143 pub fn unicode_case(&mut self, yes: bool) -> &mut Self {
145 self.flag(sys::JP_OPTION_UNICODE_CASE, yes)
146 }
147
148 pub fn ungreedy(&mut self, yes: bool) -> &mut Self {
150 self.flag(sys::JP_OPTION_UNGREEDY, yes)
151 }
152
153 pub fn anchored(&mut self, yes: bool) -> &mut Self {
155 self.flag(sys::JP_OPTION_ANCHORED, yes)
156 }
157
158 pub fn glob(&mut self, yes: bool) -> &mut Self {
160 self.flag(sys::JP_OPTION_GLOB_SYNTAX, yes)
161 }
162}
163
164#[derive(Clone, Debug)]
166pub struct Regex {
167 code: Arc<Code>,
168 pattern: Arc<str>,
169}
170
171impl Regex {
172 pub fn new(pattern: &str) -> Result<Self, Error> {
174 RegexBuilder::new().build(pattern)
175 }
176
177 pub fn as_str(&self) -> &str {
179 &self.pattern
180 }
181
182 pub fn captures_len(&self) -> usize {
184 self.code.capture_count
185 }
186
187 pub fn is_match(&self, subject: &[u8]) -> bool {
189 self.is_match_at(subject, 0)
190 }
191
192 pub fn is_match_at(&self, subject: &[u8], start: usize) -> bool {
195 self.code.is_match(subject, start)
196 }
197
198 pub fn is_full_match(&self, subject: &[u8]) -> bool {
200 self.code.is_full_match(subject)
201 }
202
203 pub fn find<'s>(&self, subject: &'s [u8]) -> Option<Match<'s>> {
205 self.find_at(subject, 0)
206 }
207
208 pub fn find_at<'s>(&self, subject: &'s [u8], start: usize) -> Option<Match<'s>> {
211 match self.scan_at(subject, start, ControlFlow::Break) {
212 ControlFlow::Break(matched) => Some(matched),
213 ControlFlow::Continue(()) => None,
214 }
215 }
216
217 pub fn captures<'s>(&self, subject: &'s [u8]) -> Option<Captures<'s>> {
219 self.captures_at(subject, 0)
220 }
221
222 pub fn captures_at<'s>(&self, subject: &'s [u8], start: usize) -> Option<Captures<'s>> {
225 self.code.captures(subject, start)
226 }
227
228 pub fn scan<'s, F, B>(&self, subject: &'s [u8], callback: F) -> ControlFlow<B>
250 where
251 F: FnMut(Match<'s>) -> ControlFlow<B>,
252 {
253 self.scan_at(subject, 0, callback)
254 }
255
256 pub fn scan_at<'s, F, B>(&self, subject: &'s [u8], start: usize, callback: F) -> ControlFlow<B>
259 where
260 F: FnMut(Match<'s>) -> ControlFlow<B>,
261 {
262 self.code.scan(subject, start, callback)
263 }
264
265 pub fn scan_captures<'s, F, B>(&self, subject: &'s [u8], callback: F) -> ControlFlow<B>
268 where
269 F: FnMut(Captures<'s>) -> ControlFlow<B>,
270 {
271 self.scan_captures_at(subject, 0, callback)
272 }
273
274 pub fn scan_captures_at<'s, F, B>(
277 &self,
278 subject: &'s [u8],
279 start: usize,
280 callback: F,
281 ) -> ControlFlow<B>
282 where
283 F: FnMut(Captures<'s>) -> ControlFlow<B>,
284 {
285 self.code.scan_captures(subject, start, callback)
286 }
287}