Skip to main content

tsrun/platform/
std_impl.rs

1//! Standard library implementations of platform traits.
2//!
3//! These implementations are only available when the `std` feature is enabled.
4
5use 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
13/// Time provider using std::time.
14pub struct StdTimeProvider {
15    /// Reference instant for timer calculations
16    epoch: Instant,
17}
18
19impl StdTimeProvider {
20    /// Create a new StdTimeProvider.
21    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
52/// Random provider using a simple xorshift64 PRNG.
53///
54/// This is a fast, decent-quality PRNG suitable for Math.random().
55/// It's seeded from the current time on creation.
56pub struct StdRandomProvider {
57    state: u64,
58}
59
60impl StdRandomProvider {
61    /// Create a new StdRandomProvider with time-based seed.
62    pub fn new() -> Self {
63        // Seed from current time
64        let seed = SystemTime::now()
65            .duration_since(UNIX_EPOCH)
66            .map(|d| d.as_nanos() as u64)
67            .unwrap_or(0x12345678_9abcdef0);
68
69        // Ensure non-zero seed
70        let seed = if seed == 0 { 0x12345678_9abcdef0 } else { seed };
71
72        Self { state: seed }
73    }
74
75    /// Create with a specific seed (for testing).
76    #[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        // xorshift64 algorithm
92        let mut x = self.state;
93        x ^= x << 13;
94        x ^= x >> 7;
95        x ^= x << 17;
96        self.state = x;
97
98        // Convert to f64 in [0, 1)
99        // Use the upper 53 bits for better distribution
100        let mantissa = x >> 11; // 53 bits
101        (mantissa as f64) / ((1u64 << 53) as f64)
102    }
103}
104
105/// Console provider using std print macros.
106///
107/// Writes to stdout for Log/Info/Debug and stderr for Warn/Error.
108pub struct StdConsoleProvider;
109
110impl StdConsoleProvider {
111    /// Create a new StdConsoleProvider.
112    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        // Print some newlines as a visual separator
137        println!("\n--- Console cleared ---\n");
138    }
139}
140
141// ═══════════════════════════════════════════════════════════════════════════════
142// FancyRegexProvider - RegExp implementation using fancy-regex crate
143// ═══════════════════════════════════════════════════════════════════════════════
144
145#[cfg(feature = "regex")]
146mod regex_impl {
147    use super::*;
148
149    /// RegExp provider using the `fancy-regex` crate.
150    ///
151    /// This is the default provider for std builds with the `regex` feature enabled.
152    /// It supports advanced regex features like lookahead, lookbehind, and backreferences.
153    #[derive(Debug, Clone, Copy, Default)]
154    pub struct FancyRegexProvider;
155
156    impl FancyRegexProvider {
157        /// Create a new FancyRegexProvider.
158        pub fn new() -> Self {
159            Self
160        }
161    }
162
163    /// Compiled regex wrapping fancy_regex::Regex.
164    #[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            // fancy_regex doesn't support replacement patterns directly,
229            // we need to handle $1, $2, etc. ourselves
230            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    /// Expand replacement string with capture group references.
267    ///
268    /// Supports: $1-$99, $&, $$
269    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                    // $$ -> literal $
285                    result.push('$');
286                    i += 2;
287                } else if next == '&' {
288                    // $& -> full match
289                    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                    // $1-$99
295                    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                    // If group doesn't exist, replace with empty string
310                    i = j;
311                } else {
312                    // Not a special sequence, keep the $
313                    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            // Convert JS regex syntax to Rust regex syntax
327            let mut regex_pattern = js_regex_to_rust(pattern);
328
329            // Build flags prefix
330            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(&regex_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    /// Convert a JavaScript regex pattern to a Rust regex pattern.
357    ///
358    /// Handles differences between JS and Rust regex syntax:
359    /// - In JS, `[` inside a character class is a literal character
360    /// - In Rust, `[` inside a character class needs to be escaped as `\[`
361    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; // True right after [ or [^
368
369        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                // Escaped character - copy both chars and skip
378                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                // Inside character class
395                if char_class_start {
396                    // First char(s) after [ have special meaning
397                    if c == '^' {
398                        result.push(c);
399                        // Still in char_class_start mode - next char could be ]
400                    } else if c == ']' {
401                        // ] right after [ or [^ is a literal ]
402                        result.push(c);
403                        char_class_start = false;
404                    } else if c == '[' {
405                        // [ at start of class - needs escaping for Rust
406                        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                    // End of character class
415                    in_char_class = false;
416                    result.push(c);
417                } else if c == '[' {
418                    // Unescaped [ inside character class - escape it for Rust
419                    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;