tsrun/platform/
std_impl.rs1use super::{ConsoleLevel, ConsoleProvider, RandomProvider, TimeProvider};
6use std::time::{Instant, SystemTime, UNIX_EPOCH};
7
8#[cfg(feature = "regex")]
9use super::{CompiledRegex, RegExpProvider, RegexMatch};
10#[cfg(feature = "regex")]
11use std::rc::Rc;
12
13pub struct StdTimeProvider {
15 epoch: Instant,
17}
18
19impl StdTimeProvider {
20 pub fn new() -> Self {
22 Self {
23 epoch: Instant::now(),
24 }
25 }
26}
27
28impl Default for StdTimeProvider {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl TimeProvider for StdTimeProvider {
35 fn now_millis(&self) -> i64 {
36 SystemTime::now()
37 .duration_since(UNIX_EPOCH)
38 .map(|d| d.as_millis() as i64)
39 .unwrap_or(0)
40 }
41
42 fn elapsed_millis(&self, start: u64) -> u64 {
43 let now = self.epoch.elapsed().as_millis() as u64;
44 now.saturating_sub(start)
45 }
46
47 fn start_timer(&self) -> u64 {
48 self.epoch.elapsed().as_millis() as u64
49 }
50}
51
52pub struct StdRandomProvider {
57 state: u64,
58}
59
60impl StdRandomProvider {
61 pub fn new() -> Self {
63 let seed = SystemTime::now()
65 .duration_since(UNIX_EPOCH)
66 .map(|d| d.as_nanos() as u64)
67 .unwrap_or(0x12345678_9abcdef0);
68
69 let seed = if seed == 0 { 0x12345678_9abcdef0 } else { seed };
71
72 Self { state: seed }
73 }
74
75 #[allow(dead_code)]
77 pub fn with_seed(seed: u64) -> Self {
78 let seed = if seed == 0 { 1 } else { seed };
79 Self { state: seed }
80 }
81}
82
83impl Default for StdRandomProvider {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl RandomProvider for StdRandomProvider {
90 fn random(&mut self) -> f64 {
91 let mut x = self.state;
93 x ^= x << 13;
94 x ^= x >> 7;
95 x ^= x << 17;
96 self.state = x;
97
98 let mantissa = x >> 11; (mantissa as f64) / ((1u64 << 53) as f64)
102 }
103}
104
105pub struct StdConsoleProvider;
109
110impl StdConsoleProvider {
111 pub fn new() -> Self {
113 Self
114 }
115}
116
117impl Default for StdConsoleProvider {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl ConsoleProvider for StdConsoleProvider {
124 fn write(&self, level: ConsoleLevel, message: &str) {
125 match level {
126 ConsoleLevel::Log | ConsoleLevel::Info | ConsoleLevel::Debug => {
127 println!("{message}");
128 }
129 ConsoleLevel::Warn | ConsoleLevel::Error => {
130 eprintln!("{message}");
131 }
132 }
133 }
134
135 fn clear(&self) {
136 println!("\n--- Console cleared ---\n");
138 }
139}
140
141#[cfg(feature = "regex")]
146mod regex_impl {
147 use super::*;
148
149 #[derive(Debug, Clone, Copy, Default)]
154 pub struct FancyRegexProvider;
155
156 impl FancyRegexProvider {
157 pub fn new() -> Self {
159 Self
160 }
161 }
162
163 #[derive(Debug)]
165 pub struct FancyCompiledRegex {
166 regex: fancy_regex::Regex,
167 #[allow(dead_code)]
168 flags: String,
169 }
170
171 impl CompiledRegex for FancyCompiledRegex {
172 fn is_match(&self, input: &str) -> Result<bool, String> {
173 self.regex.is_match(input).map_err(|e| e.to_string())
174 }
175
176 fn find(&self, input: &str, start_pos: usize) -> Result<Option<RegexMatch>, String> {
177 let slice = input.get(start_pos..).unwrap_or("");
178 match self.regex.captures(slice) {
179 Ok(Some(caps)) => {
180 let full_match = caps.get(0).ok_or("No match found")?;
181 let captures: Vec<Option<(usize, usize)>> = caps
182 .iter()
183 .map(|m| m.map(|c| (start_pos + c.start(), start_pos + c.end())))
184 .collect();
185 Ok(Some(RegexMatch {
186 start: start_pos + full_match.start(),
187 end: start_pos + full_match.end(),
188 captures,
189 }))
190 }
191 Ok(None) => Ok(None),
192 Err(e) => Err(e.to_string()),
193 }
194 }
195
196 fn find_iter(&self, input: &str) -> Result<Vec<RegexMatch>, String> {
197 let mut results = Vec::new();
198 for caps_result in self.regex.captures_iter(input) {
199 match caps_result {
200 Ok(caps) => {
201 let full_match = caps.get(0).ok_or("No match found")?;
202 let captures: Vec<Option<(usize, usize)>> = caps
203 .iter()
204 .map(|m| m.map(|c| (c.start(), c.end())))
205 .collect();
206 results.push(RegexMatch {
207 start: full_match.start(),
208 end: full_match.end(),
209 captures,
210 });
211 }
212 Err(e) => return Err(e.to_string()),
213 }
214 }
215 Ok(results)
216 }
217
218 fn split(&self, input: &str) -> Result<Vec<String>, String> {
219 let parts: Result<Vec<_>, _> = self
220 .regex
221 .split(input)
222 .map(|r| r.map(|s| s.to_string()))
223 .collect();
224 parts.map_err(|e| e.to_string())
225 }
226
227 fn replace(&self, input: &str, replacement: &str) -> Result<String, String> {
228 match self.regex.captures(input) {
231 Ok(Some(caps)) => {
232 let full_match = caps.get(0).ok_or("No match found")?;
233 let replacement_str = expand_replacement(replacement, &caps);
234 let mut result = String::with_capacity(input.len());
235 result.push_str(input.get(..full_match.start()).unwrap_or(""));
236 result.push_str(&replacement_str);
237 result.push_str(input.get(full_match.end()..).unwrap_or(""));
238 Ok(result)
239 }
240 Ok(None) => Ok(input.to_string()),
241 Err(e) => Err(e.to_string()),
242 }
243 }
244
245 fn replace_all(&self, input: &str, replacement: &str) -> Result<String, String> {
246 let mut result = String::with_capacity(input.len());
247 let mut last_end = 0;
248
249 for caps_result in self.regex.captures_iter(input) {
250 match caps_result {
251 Ok(caps) => {
252 let full_match = caps.get(0).ok_or("No match found")?;
253 let replacement_str = expand_replacement(replacement, &caps);
254 result.push_str(input.get(last_end..full_match.start()).unwrap_or(""));
255 result.push_str(&replacement_str);
256 last_end = full_match.end();
257 }
258 Err(e) => return Err(e.to_string()),
259 }
260 }
261 result.push_str(input.get(last_end..).unwrap_or(""));
262 Ok(result)
263 }
264 }
265
266 fn expand_replacement(replacement: &str, caps: &fancy_regex::Captures) -> String {
270 let mut result = String::with_capacity(replacement.len());
271 let chars: Vec<char> = replacement.chars().collect();
272 let mut i = 0;
273
274 while i < chars.len() {
275 let Some(&c) = chars.get(i) else { break };
276 let Some(&next) = chars.get(i + 1) else {
277 result.push(c);
278 i += 1;
279 continue;
280 };
281
282 if c == '$' {
283 if next == '$' {
284 result.push('$');
286 i += 2;
287 } else if next == '&' {
288 if let Some(m) = caps.get(0) {
290 result.push_str(m.as_str());
291 }
292 i += 2;
293 } else if next.is_ascii_digit() {
294 let mut num_str = String::new();
296 let mut j = i + 1;
297 while let Some(&ch) = chars.get(j) {
298 if !ch.is_ascii_digit() || num_str.len() >= 2 {
299 break;
300 }
301 num_str.push(ch);
302 j += 1;
303 }
304 if let Ok(group_num) = num_str.parse::<usize>()
305 && let Some(m) = caps.get(group_num)
306 {
307 result.push_str(m.as_str());
308 }
309 i = j;
311 } else {
312 result.push('$');
314 i += 1;
315 }
316 } else {
317 result.push(c);
318 i += 1;
319 }
320 }
321 result
322 }
323
324 impl RegExpProvider for FancyRegexProvider {
325 fn compile(&self, pattern: &str, flags: &str) -> Result<Rc<dyn CompiledRegex>, String> {
326 let mut regex_pattern = js_regex_to_rust(pattern);
328
329 let mut prefix = String::new();
331
332 if flags.contains('i') {
333 prefix.push('i');
334 }
335 if flags.contains('m') {
336 prefix.push('m');
337 }
338 if flags.contains('s') {
339 prefix.push('s');
340 }
341
342 if !prefix.is_empty() {
343 regex_pattern = format!("(?{}){}", prefix, regex_pattern);
344 }
345
346 let regex = fancy_regex::Regex::new(®ex_pattern)
347 .map_err(|e| format!("Invalid regular expression: {}", e))?;
348
349 Ok(Rc::new(FancyCompiledRegex {
350 regex,
351 flags: flags.to_string(),
352 }))
353 }
354 }
355
356 fn js_regex_to_rust(pattern: &str) -> String {
362 let mut result = String::with_capacity(pattern.len() + 16);
363 let chars: Vec<char> = pattern.chars().collect();
364 let len = chars.len();
365 let mut i = 0;
366 let mut in_char_class = false;
367 let mut char_class_start = false; while i < len {
370 let Some(c) = chars.get(i).copied() else {
371 break;
372 };
373
374 if c == '\\'
375 && let Some(next) = chars.get(i + 1).copied()
376 {
377 result.push(c);
379 result.push(next);
380 i += 2;
381 char_class_start = false;
382 continue;
383 }
384
385 if !in_char_class {
386 if c == '[' {
387 in_char_class = true;
388 char_class_start = true;
389 result.push(c);
390 } else {
391 result.push(c);
392 }
393 } else {
394 if char_class_start {
396 if c == '^' {
398 result.push(c);
399 } else if c == ']' {
401 result.push(c);
403 char_class_start = false;
404 } else if c == '[' {
405 result.push('\\');
407 result.push('[');
408 char_class_start = false;
409 } else {
410 result.push(c);
411 char_class_start = false;
412 }
413 } else if c == ']' {
414 in_char_class = false;
416 result.push(c);
417 } else if c == '[' {
418 result.push('\\');
420 result.push('[');
421 } else {
422 result.push(c);
423 }
424 }
425 i += 1;
426 }
427
428 result
429 }
430}
431
432#[cfg(feature = "regex")]
433pub use regex_impl::FancyRegexProvider;