Skip to main content

awk_rs/interpreter/
builtins.rs

1use std::io::Write;
2
3use crate::ast::Expr;
4use crate::error::{Error, Result, SourceLocation};
5use crate::value::Value;
6
7use super::Interpreter;
8
9impl<'a> Interpreter<'a> {
10    /// Call a function with special handling for builtins that need AST access
11    pub fn call_function<W: Write>(
12        &mut self,
13        name: &str,
14        args: &[Expr],
15        location: SourceLocation,
16        output: &mut W,
17    ) -> Result<Value> {
18        // Check for built-in functions that need special argument handling
19        match name {
20            "sub" | "gsub" => return self.call_regex_sub(name, args, location),
21            "match" => return self.call_match(args, location),
22            "split" => return self.call_split(args, location),
23            "patsplit" => return self.call_patsplit(args, location),
24            "asort" | "asorti" => return self.call_asort(name == "asorti", args, location),
25            "close" => return self.call_close(args, location),
26            "fflush" => return self.call_fflush(args, location, output),
27            "system" => return self.call_system(args, output),
28            "length" => {
29                // gawk extension: length(array) returns element count.
30                // Only intercept when the sole argument names a known array;
31                // otherwise fall through to the normal scalar handling below.
32                if let Some(Expr::Var(var_name, _)) = args.first() {
33                    // Resolve pass-by-reference aliases so length(a) works on
34                    // arrays received as function parameters.
35                    if let Some(arr) = self.arrays.get(self.resolve_array_name(var_name)) {
36                        return Ok(Value::Number(arr.len() as f64));
37                    }
38                }
39            }
40            _ => {}
41        }
42
43        // Evaluate all arguments for other functions
44        let arg_values: Result<Vec<Value>> = args.iter().map(|e| self.eval_expr(e)).collect();
45        let arg_values = arg_values?;
46
47        // Check for other built-in functions
48        if let Some(result) = self.call_builtin(name, &arg_values)? {
49            return Ok(result);
50        }
51
52        // Check for user-defined functions
53        if let Some(func) = self.functions.get(name).cloned() {
54            // Extract array names from arguments for pass-by-reference
55            let array_refs: Vec<Option<String>> = args
56                .iter()
57                .map(|e| match e {
58                    Expr::Var(name, _) if self.arrays.contains_key(name) => Some(name.clone()),
59                    _ => None,
60                })
61                .collect();
62            return self.call_user_function(func, arg_values, array_refs, output);
63        }
64
65        Err(Error::runtime_at(
66            format!("undefined function: {}", name),
67            location.line,
68            location.column,
69        ))
70    }
71
72    /// Extract regex pattern from an expression (handles both regex literals and strings)
73    fn extract_pattern(&mut self, expr: &Expr) -> Result<String> {
74        match expr {
75            Expr::Regex(pattern, _) => Ok(pattern.clone()),
76            other => Ok(self.eval_expr(other)?.to_string_val()),
77        }
78    }
79
80    /// Call sub or gsub with proper regex and target handling
81    fn call_regex_sub(
82        &mut self,
83        name: &str,
84        args: &[Expr],
85        _location: SourceLocation,
86    ) -> Result<Value> {
87        let global = name == "gsub";
88
89        let pattern = args
90            .first()
91            .map(|e| self.extract_pattern(e))
92            .transpose()?
93            .unwrap_or_default();
94
95        let replacement = args
96            .get(1)
97            .map(|e| self.eval_expr(e))
98            .transpose()?
99            .map(|v| v.to_string_val())
100            .unwrap_or_default();
101
102        // Get the target (third argument or $0)
103        let (target_value, target_expr) = if let Some(target_arg) = args.get(2) {
104            (
105                self.eval_expr(target_arg)?.to_string_val(),
106                Some(target_arg),
107            )
108        } else {
109            (self.record.clone(), None)
110        };
111
112        let re = self.get_regex(&pattern)?.clone();
113
114        let (new_str, count) = regex_sub_helper(&re, &replacement, &target_value, global);
115
116        // Assign the result back to the target
117        if let Some(target_arg) = target_expr {
118            self.assign_to_lvalue(target_arg, Value::from_string(new_str))?;
119        } else {
120            self.set_record(&new_str);
121        }
122
123        Ok(Value::Number(count as f64))
124    }
125
126    /// Call match with proper regex handling
127    fn call_match(&mut self, args: &[Expr], _location: SourceLocation) -> Result<Value> {
128        let s = args
129            .first()
130            .map(|e| self.eval_expr(e))
131            .transpose()?
132            .map(|v| v.to_string_val())
133            .unwrap_or_default();
134
135        let pattern = args
136            .get(1)
137            .map(|e| self.extract_pattern(e))
138            .transpose()?
139            .unwrap_or_default();
140
141        let re = self.get_regex(&pattern)?.clone();
142
143        if let Some(m) = re.find(&s) {
144            self.rstart = m.start() + 1;
145            self.rlength = m.len() as i32;
146            Ok(Value::Number(self.rstart as f64))
147        } else {
148            self.rstart = 0;
149            self.rlength = -1;
150            Ok(Value::Number(0.0))
151        }
152    }
153
154    /// Call split with proper array name handling
155    fn call_split(&mut self, args: &[Expr], location: SourceLocation) -> Result<Value> {
156        let s = args
157            .first()
158            .map(|e| self.eval_expr(e))
159            .transpose()?
160            .map(|v| v.to_string_val())
161            .unwrap_or_default();
162
163        // Get array name from second argument (must be a variable name)
164        let array_name = match args.get(1) {
165            Some(Expr::Var(name, _)) => name.clone(),
166            Some(Expr::ArrayAccess { array, .. }) => array.clone(),
167            Some(_) => {
168                return Err(Error::runtime_at(
169                    "split: second argument must be an array",
170                    location.line,
171                    location.column,
172                ));
173            }
174            None => {
175                return Err(Error::runtime_at(
176                    "split: missing array argument",
177                    location.line,
178                    location.column,
179                ));
180            }
181        };
182
183        // Get separator (third argument or default FS)
184        let sep = if let Some(sep_expr) = args.get(2) {
185            self.extract_pattern(sep_expr)?
186        } else {
187            self.fs.clone()
188        };
189
190        // Clear the array
191        self.arrays.remove(&array_name);
192
193        // Split and populate array
194        let parts: Vec<&str> = if sep.is_empty() {
195            // gawk extension: empty separator splits into one element per character
196            s.char_indices()
197                .map(|(start, c)| &s[start..start + c.len_utf8()])
198                .collect()
199        } else if sep == " " {
200            s.split_whitespace().collect()
201        } else if sep.len() == 1 {
202            s.split(&sep).collect()
203        } else {
204            // Use regex split for multi-char separators (via the shared regex cache)
205            let re = self.get_regex(&sep)?.clone();
206            re.split(&s).collect()
207        };
208
209        for (i, part) in parts.iter().enumerate() {
210            let key = (i + 1).to_string();
211            self.set_array_element(&array_name, &key, Value::from_string(part.to_string()));
212        }
213
214        Ok(Value::Number(parts.len() as f64))
215    }
216
217    /// asort(source [, dest]) - sort array values
218    /// asorti(source [, dest]) - sort array indices
219    fn call_asort(
220        &mut self,
221        sort_indices: bool,
222        args: &[Expr],
223        location: SourceLocation,
224    ) -> Result<Value> {
225        // Get source array name
226        let source_name = match args.first() {
227            Some(Expr::Var(name, _)) => name.clone(),
228            _ => {
229                return Err(Error::runtime_at(
230                    if sort_indices {
231                        "asorti: first argument must be an array"
232                    } else {
233                        "asort: first argument must be an array"
234                    },
235                    location.line,
236                    location.column,
237                ));
238            }
239        };
240
241        // Get optional destination array name
242        let dest_name = match args.get(1) {
243            Some(Expr::Var(name, _)) => Some(name.clone()),
244            None => None,
245            _ => {
246                return Err(Error::runtime_at(
247                    if sort_indices {
248                        "asorti: second argument must be an array"
249                    } else {
250                        "asort: second argument must be an array"
251                    },
252                    location.line,
253                    location.column,
254                ));
255            }
256        };
257
258        // Get values to sort
259        let items: Vec<String> = if let Some(arr) = self.arrays.get(&source_name) {
260            if sort_indices {
261                arr.keys().cloned().collect()
262            } else {
263                arr.values().map(|v| v.to_string_val()).collect()
264            }
265        } else {
266            Vec::new()
267        };
268
269        let mut sorted = items;
270        sorted.sort();
271
272        let count = sorted.len();
273
274        // Store in destination (or source if no dest)
275        let target = dest_name.unwrap_or_else(|| source_name.clone());
276        self.arrays.remove(&target);
277
278        for (i, item) in sorted.iter().enumerate() {
279            let key = (i + 1).to_string();
280            self.set_array_element(&target, &key, Value::from_string(item.clone()));
281        }
282
283        Ok(Value::Number(count as f64))
284    }
285
286    /// patsplit(string, array, fieldpat [, seps]) - split by pattern matches
287    fn call_patsplit(&mut self, args: &[Expr], location: SourceLocation) -> Result<Value> {
288        // Get string to split
289        let s = args
290            .first()
291            .map(|e| self.eval_expr(e))
292            .transpose()?
293            .map(|v| v.to_string_val())
294            .unwrap_or_default();
295
296        // Get array name
297        let array_name = match args.get(1) {
298            Some(Expr::Var(name, _)) => name.clone(),
299            _ => {
300                return Err(Error::runtime_at(
301                    "patsplit: second argument must be an array",
302                    location.line,
303                    location.column,
304                ));
305            }
306        };
307
308        // Get field pattern
309        let fieldpat = if let Some(pat_expr) = args.get(2) {
310            self.extract_pattern(pat_expr)?
311        } else {
312            return Err(Error::runtime_at(
313                "patsplit: missing fieldpat argument",
314                location.line,
315                location.column,
316            ));
317        };
318
319        // Optional separator array
320        let seps_name = match args.get(3) {
321            Some(Expr::Var(name, _)) => Some(name.clone()),
322            None => None,
323            _ => None,
324        };
325
326        // Clear destination arrays
327        self.arrays.remove(&array_name);
328        if let Some(ref name) = seps_name {
329            self.arrays.remove(name);
330        }
331
332        // Compile regex and find all matches
333        let re = self.get_regex(&fieldpat)?;
334        let matches: Vec<regex::Match> = re.find_iter(&s).collect();
335
336        // Store matches in array
337        for (i, mat) in matches.iter().enumerate() {
338            let key = (i + 1).to_string();
339            self.set_array_element(
340                &array_name,
341                &key,
342                Value::from_string(mat.as_str().to_string()),
343            );
344        }
345
346        // Store separators if requested
347        if let Some(ref name) = seps_name {
348            let mut last_end = 0;
349            for (i, mat) in matches.iter().enumerate() {
350                let sep = &s[last_end..mat.start()];
351                let key = i.to_string();
352                self.set_array_element(name, &key, Value::from_string(sep.to_string()));
353                last_end = mat.end();
354            }
355            // Final separator after last match
356            let key = matches.len().to_string();
357            self.set_array_element(name, &key, Value::from_string(s[last_end..].to_string()));
358        }
359
360        Ok(Value::Number(matches.len() as f64))
361    }
362
363    /// Call close to close a file or pipe
364    fn call_close(&mut self, args: &[Expr], location: SourceLocation) -> Result<Value> {
365        let filename = args
366            .first()
367            .map(|e| self.eval_expr(e))
368            .transpose()?
369            .map(|v| v.to_string_val())
370            .unwrap_or_default();
371
372        // Remove from any of our file/pipe collections
373        let found = self.output_files.remove(&filename).is_some()
374            || self.input_files.remove(&filename).is_some()
375            || self.pipes.remove(&filename).is_some();
376
377        let _ = location;
378        Ok(Value::Number(if found { 0.0 } else { -1.0 }))
379    }
380
381    /// Call fflush to flush output
382    fn call_fflush<W: Write>(
383        &mut self,
384        args: &[Expr],
385        _location: SourceLocation,
386        output: &mut W,
387    ) -> Result<Value> {
388        if args.is_empty() {
389            // Flush all output
390            output.flush().map_err(Error::Io)?;
391            for file in self.output_files.values_mut() {
392                let _ = file.flush();
393            }
394            Ok(Value::Number(0.0))
395        } else {
396            let filename = self.eval_expr(&args[0])?.to_string_val();
397            if filename.is_empty() {
398                output.flush().map_err(Error::Io)?;
399                Ok(Value::Number(0.0))
400            } else if let Some(file) = self.output_files.get_mut(&filename) {
401                file.flush().map_err(Error::Io)?;
402                Ok(Value::Number(0.0))
403            } else {
404                Ok(Value::Number(-1.0))
405            }
406        }
407    }
408
409    /// `system(cmd)`: POSIX requires all buffered output to be flushed before the
410    /// command runs, so its output interleaves with ours in the expected order.
411    fn call_system<W: Write>(&mut self, args: &[Expr], output: &mut W) -> Result<Value> {
412        let cmd = match args.first() {
413            Some(expr) => self.eval_expr(expr)?.to_string_val(),
414            None => String::new(),
415        };
416
417        output.flush().map_err(Error::Io)?;
418        for file in self.output_files.values_mut() {
419            let _ = file.flush();
420        }
421
422        let status = std::process::Command::new("sh")
423            .arg("-c")
424            .arg(&cmd)
425            .status()
426            .map(|s| s.code().unwrap_or(-1))
427            .unwrap_or(-1);
428        Ok(Value::Number(status as f64))
429    }
430
431    fn call_builtin(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>> {
432        match name {
433            // String functions
434            "length" => {
435                let s = args
436                    .first()
437                    .map(|v| v.to_string_val())
438                    .unwrap_or_else(|| self.record.clone());
439                // Use character count for UTF-8 support
440                Ok(Some(Value::Number(s.chars().count() as f64)))
441            }
442
443            "substr" => {
444                let s = args.first().map(|v| v.to_string_val()).unwrap_or_default();
445                let start = args.get(1).map(|v| v.to_number() as usize).unwrap_or(1);
446                let len = args.get(2).map(|v| v.to_number() as usize);
447
448                // AWK uses 1-based indexing; ensure start is at least 1
449                let start = start.max(1).saturating_sub(1);
450                let result = if let Some(len) = len {
451                    s.chars().skip(start).take(len).collect()
452                } else {
453                    s.chars().skip(start).collect()
454                };
455                Ok(Some(Value::from_string(result)))
456            }
457
458            "index" => {
459                let s = args.first().map(|v| v.to_string_val()).unwrap_or_default();
460                let target = args.get(1).map(|v| v.to_string_val()).unwrap_or_default();
461                // Find byte position, then convert to character position
462                let pos = s
463                    .find(&target)
464                    .map(|byte_idx| {
465                        // Count characters before the byte index
466                        s[..byte_idx].chars().count() + 1
467                    })
468                    .unwrap_or(0);
469                Ok(Some(Value::Number(pos as f64)))
470            }
471
472            "sprintf" => {
473                let format = args.first().map(|v| v.to_string_val()).unwrap_or_default();
474                let rest = if args.len() > 1 { &args[1..] } else { &[] };
475                let result = self.format_printf(&format, rest);
476                Ok(Some(Value::from_string(result)))
477            }
478
479            "tolower" => {
480                let s = args.first().map(|v| v.to_string_val()).unwrap_or_default();
481                Ok(Some(Value::from_string(s.to_lowercase())))
482            }
483
484            "toupper" => {
485                let s = args.first().map(|v| v.to_string_val()).unwrap_or_default();
486                Ok(Some(Value::from_string(s.to_uppercase())))
487            }
488
489            // Math functions
490            "sin" => {
491                let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
492                Ok(Some(Value::Number(n.sin())))
493            }
494
495            "cos" => {
496                let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
497                Ok(Some(Value::Number(n.cos())))
498            }
499
500            "atan2" => {
501                let y = args.first().map(|v| v.to_number()).unwrap_or(0.0);
502                let x = args.get(1).map(|v| v.to_number()).unwrap_or(0.0);
503                Ok(Some(Value::Number(y.atan2(x))))
504            }
505
506            "exp" => {
507                let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
508                Ok(Some(Value::Number(n.exp())))
509            }
510
511            "log" => {
512                let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
513                Ok(Some(Value::Number(n.ln())))
514            }
515
516            "sqrt" => {
517                let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
518                Ok(Some(Value::Number(n.sqrt())))
519            }
520
521            "int" => {
522                let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
523                Ok(Some(Value::Number(n.trunc())))
524            }
525
526            "rand" => {
527                // Use the internal RNG state
528                let random = self.next_random();
529                Ok(Some(Value::Number(random)))
530            }
531
532            "srand" => {
533                let old_seed = self.rand_seed;
534                if let Some(seed) = args.first() {
535                    self.rand_seed = seed.to_number() as u64;
536                } else {
537                    use std::time::{SystemTime, UNIX_EPOCH};
538                    self.rand_seed = SystemTime::now()
539                        .duration_since(UNIX_EPOCH)
540                        .map(|d| d.as_secs())
541                        .unwrap_or(0);
542                }
543                self.rand_state = self.rand_seed;
544                Ok(Some(Value::Number(old_seed as f64)))
545            }
546
547            // System functions
548            // === GAWK Extensions ===
549
550            // Time functions
551            "systime" => {
552                // Return current time as seconds since epoch
553                use std::time::{SystemTime, UNIX_EPOCH};
554                let secs = SystemTime::now()
555                    .duration_since(UNIX_EPOCH)
556                    .map(|d| d.as_secs())
557                    .unwrap_or(0);
558                Ok(Some(Value::Number(secs as f64)))
559            }
560
561            "mktime" => {
562                // Parse "YYYY MM DD HH MM SS [DST]" into epoch timestamp
563                let datespec = args.first().map(|v| v.to_string_val()).unwrap_or_default();
564                let parts: Vec<i64> = datespec
565                    .split_whitespace()
566                    .filter_map(|s| s.parse().ok())
567                    .collect();
568
569                if parts.len() >= 6 {
570                    // Simple implementation using chrono-like calculation
571                    // This is a simplified version; for full accuracy we'd need chrono crate
572                    let year = parts[0];
573                    let month = parts[1];
574                    let day = parts[2];
575                    let hour = parts[3];
576                    let min = parts[4];
577                    let sec = parts[5];
578
579                    // Simplified epoch calculation (not handling DST or timezones)
580                    let epoch = simple_mktime(year, month, day, hour, min, sec);
581                    Ok(Some(Value::Number(epoch as f64)))
582                } else {
583                    Ok(Some(Value::Number(-1.0)))
584                }
585            }
586
587            "strftime" => {
588                // Format timestamp
589                let format = args
590                    .first()
591                    .map(|v| v.to_string_val())
592                    .unwrap_or_else(|| "%a %b %e %H:%M:%S %Z %Y".to_string());
593                use std::time::{SystemTime, UNIX_EPOCH};
594                let timestamp = args
595                    .get(1)
596                    .map(|v| v.to_number() as u64)
597                    .unwrap_or_else(|| {
598                        SystemTime::now()
599                            .duration_since(UNIX_EPOCH)
600                            .map(|d| d.as_secs())
601                            .unwrap_or(0)
602                    });
603
604                let result = format_strftime(&format, timestamp);
605                Ok(Some(Value::from_string(result)))
606            }
607
608            // gensub - like gsub but returns the result instead of modifying in place
609            "gensub" => {
610                let pattern = args.first().map(|v| v.to_string_val()).unwrap_or_default();
611                let replacement = args.get(1).map(|v| v.to_string_val()).unwrap_or_default();
612                let how = args
613                    .get(2)
614                    .map(|v| v.to_string_val())
615                    .unwrap_or_else(|| "g".to_string());
616                let target = args
617                    .get(3)
618                    .map(|v| v.to_string_val())
619                    .unwrap_or_else(|| self.record.clone());
620
621                let re = self.get_regex(&pattern)?.clone();
622
623                // "g" or "G" means global, otherwise it's the occurrence number
624                let result = if how.eq_ignore_ascii_case("g") {
625                    let mut result = String::new();
626                    let mut last_end = 0;
627                    for caps in re.captures_iter(&target) {
628                        let m = caps.get(0).unwrap();
629                        result.push_str(&target[last_end..m.start()]);
630                        result.push_str(&apply_replacement(&replacement, &caps, true));
631                        last_end = m.end();
632                    }
633                    result.push_str(&target[last_end..]);
634                    result
635                } else {
636                    // Numeric occurrence (default to the 1st if unparseable, per gawk)
637                    let n = how.parse::<usize>().unwrap_or(1).max(1);
638                    let mut count = 0;
639                    let mut last_end = 0;
640                    let mut result = String::new();
641                    let mut replaced = false;
642                    for caps in re.captures_iter(&target) {
643                        count += 1;
644                        if count == n {
645                            let m = caps.get(0).unwrap();
646                            result.push_str(&target[last_end..m.start()]);
647                            result.push_str(&apply_replacement(&replacement, &caps, true));
648                            last_end = m.end();
649                            replaced = true;
650                            break;
651                        }
652                    }
653                    if !replaced {
654                        target.clone()
655                    } else {
656                        result.push_str(&target[last_end..]);
657                        result
658                    }
659                };
660
661                Ok(Some(Value::from_string(result)))
662            }
663
664            _ => Ok(None), // Not a built-in
665        }
666    }
667
668    fn call_user_function<W: Write>(
669        &mut self,
670        func: &crate::ast::FunctionDef,
671        args: Vec<Value>,
672        array_refs: Vec<Option<String>>,
673        output: &mut W,
674    ) -> Result<Value> {
675        // Save current variables for local scope
676        let saved_vars: std::collections::HashMap<String, Value> = func
677            .params
678            .iter()
679            .filter_map(|name| self.variables.get(name).map(|v| (name.clone(), v.clone())))
680            .collect();
681
682        // Save any arrays that share names with parameters (for local arrays)
683        let saved_arrays: std::collections::HashMap<
684            String,
685            std::collections::HashMap<String, Value>,
686        > = func
687            .params
688            .iter()
689            .filter_map(|name| self.arrays.get(name).map(|a| (name.clone(), a.clone())))
690            .collect();
691
692        // Create array aliases for pass-by-reference
693        // If an argument is an array reference, the parameter name should point to the same array
694        let mut array_aliases: std::collections::HashMap<String, String> =
695            std::collections::HashMap::new();
696        for (i, param) in func.params.iter().enumerate() {
697            if let Some(Some(array_name)) = array_refs.get(i) {
698                // This parameter is an array reference
699                // Create an alias: when we access param, we should access array_name
700                if param != array_name {
701                    array_aliases.insert(param.clone(), array_name.clone());
702                }
703            }
704        }
705        self.array_aliases = array_aliases;
706
707        // Set scalar parameters
708        for (i, param) in func.params.iter().enumerate() {
709            // Skip if this is an array reference
710            if let Some(Some(_)) = array_refs.get(i) {
711                continue;
712            }
713            let value = args.get(i).cloned().unwrap_or(Value::Uninitialized);
714            self.set_variable_value(param, value);
715        }
716
717        // Execute function body, passing the actual output
718        let result = match self.execute_block(&func.body, output)? {
719            super::stmt::StmtResult::Return(v) => v,
720            _ => Value::Uninitialized,
721        };
722
723        // Clear array aliases
724        self.array_aliases.clear();
725
726        // Restore saved variables and remove parameters that weren't saved
727        for param in &func.params {
728            if let Some(value) = saved_vars.get(param) {
729                self.set_variable_value(param, value.clone());
730            } else {
731                self.variables.remove(param);
732            }
733        }
734
735        // Restore any saved arrays or remove local arrays
736        for param in &func.params {
737            if let Some(arr) = saved_arrays.get(param) {
738                self.arrays.insert(param.clone(), arr.clone());
739            } else if !array_refs
740                .get(
741                    func.params
742                        .iter()
743                        .position(|p| p == param)
744                        .unwrap_or(usize::MAX),
745                )
746                .map(|r| r.is_some())
747                .unwrap_or(false)
748            {
749                self.arrays.remove(param);
750            }
751        }
752
753        Ok(result)
754    }
755
756    /// Generate a random number between 0 and 1 using xorshift64
757    fn next_random(&mut self) -> f64 {
758        let mut x = self.rand_state;
759        x ^= x << 13;
760        x ^= x >> 7;
761        x ^= x << 17;
762        self.rand_state = x;
763        (x as f64) / (u64::MAX as f64)
764    }
765}
766
767fn regex_sub_helper(
768    re: &regex::Regex,
769    replacement: &str,
770    target: &str,
771    global: bool,
772) -> (String, usize) {
773    // POSIX/gawk replacement semantics: `&` = matched text, `\&` = literal `&`,
774    // `\\` = literal backslash. sub/gsub don't support `\N` group refs.
775    let mut count = 0;
776
777    if global {
778        let result = re.replace_all(target, |caps: &regex::Captures| {
779            count += 1;
780            apply_replacement(replacement, caps, false)
781        });
782        (result.to_string(), count)
783    } else {
784        let result = re.replace(target, |caps: &regex::Captures| {
785            count += 1;
786            apply_replacement(replacement, caps, false)
787        });
788        (result.to_string(), count)
789    }
790}
791
792/// Expand a sub/gsub/gensub replacement string against a regex match.
793///
794/// - `&` is replaced with the whole match text.
795/// - `\&` is a literal `&`.
796/// - `\\` is a literal backslash.
797/// - When `allow_groups` is set (gensub only), `\1`..`\9` are replaced with
798///   the corresponding capture group (empty string if the group didn't
799///   participate in the match).
800/// - Any other backslash sequence is passed through unchanged.
801fn apply_replacement(replacement: &str, caps: &regex::Captures, allow_groups: bool) -> String {
802    let mut result = String::new();
803    let mut chars = replacement.chars().peekable();
804
805    while let Some(c) = chars.next() {
806        match c {
807            '\\' => match chars.peek().copied() {
808                Some('&') => {
809                    result.push('&');
810                    chars.next();
811                }
812                Some('\\') => {
813                    result.push('\\');
814                    chars.next();
815                }
816                Some(d) if allow_groups && d.is_ascii_digit() && d != '0' => {
817                    chars.next();
818                    let idx = d.to_digit(10).unwrap() as usize;
819                    if let Some(m) = caps.get(idx) {
820                        result.push_str(m.as_str());
821                    }
822                }
823                _ => result.push('\\'),
824            },
825            '&' => {
826                if let Some(m) = caps.get(0) {
827                    result.push_str(m.as_str());
828                }
829            }
830            other => result.push(other),
831        }
832    }
833
834    result
835}
836
837/// Simplified mktime implementation (UTC-based)
838fn simple_mktime(year: i64, month: i64, day: i64, hour: i64, min: i64, sec: i64) -> i64 {
839    // Days in each month (non-leap year)
840    const DAYS_IN_MONTH: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
841
842    fn is_leap_year(year: i64) -> bool {
843        (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
844    }
845
846    fn days_in_year(year: i64) -> i64 {
847        if is_leap_year(year) { 366 } else { 365 }
848    }
849
850    // Calculate days from epoch (1970-01-01)
851    let mut days: i64 = 0;
852
853    // Add days for complete years
854    for y in 1970..year {
855        days += days_in_year(y);
856    }
857    for y in year..1970 {
858        days -= days_in_year(y);
859    }
860
861    // Add days for complete months in current year
862    for m in 1..month {
863        let m_idx = (m - 1) as usize;
864        if m_idx < 12 {
865            days += DAYS_IN_MONTH[m_idx];
866            if m == 2 && is_leap_year(year) {
867                days += 1;
868            }
869        }
870    }
871
872    // Add remaining days
873    days += day - 1;
874
875    // Convert to seconds
876    days * 86400 + hour * 3600 + min * 60 + sec
877}
878
879/// Simplified strftime implementation
880fn format_strftime(format: &str, timestamp: u64) -> String {
881    // Break down timestamp into components
882    let secs = timestamp as i64;
883
884    // Calculate year, month, day, etc.
885    let (year, month, day, hour, min, sec, wday, yday) = breakdown_time(secs);
886
887    let weekday_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
888    let month_names = [
889        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
890    ];
891    let weekday_full = [
892        "Sunday",
893        "Monday",
894        "Tuesday",
895        "Wednesday",
896        "Thursday",
897        "Friday",
898        "Saturday",
899    ];
900    let month_full = [
901        "January",
902        "February",
903        "March",
904        "April",
905        "May",
906        "June",
907        "July",
908        "August",
909        "September",
910        "October",
911        "November",
912        "December",
913    ];
914
915    let mut result = String::new();
916    let mut chars = format.chars().peekable();
917
918    while let Some(ch) = chars.next() {
919        if ch == '%' {
920            match chars.next() {
921                Some('Y') => result.push_str(&format!("{:04}", year)),
922                Some('y') => result.push_str(&format!("{:02}", year % 100)),
923                Some('m') => result.push_str(&format!("{:02}", month)),
924                Some('d') => result.push_str(&format!("{:02}", day)),
925                Some('e') => result.push_str(&format!("{:2}", day)),
926                Some('H') => result.push_str(&format!("{:02}", hour)),
927                Some('M') => result.push_str(&format!("{:02}", min)),
928                Some('S') => result.push_str(&format!("{:02}", sec)),
929                Some('a') => result.push_str(weekday_names.get(wday as usize).unwrap_or(&"???")),
930                Some('A') => result.push_str(weekday_full.get(wday as usize).unwrap_or(&"???")),
931                Some('b') | Some('h') => {
932                    result.push_str(month_names.get((month - 1) as usize).unwrap_or(&"???"))
933                }
934                Some('B') => {
935                    result.push_str(month_full.get((month - 1) as usize).unwrap_or(&"???"))
936                }
937                Some('j') => result.push_str(&format!("{:03}", yday)),
938                Some('u') => result.push_str(&format!("{}", if wday == 0 { 7 } else { wday })),
939                Some('w') => result.push_str(&format!("{}", wday)),
940                Some('Z') => result.push_str("UTC"),
941                Some('z') => result.push_str("+0000"),
942                Some('%') => result.push('%'),
943                Some('n') => result.push('\n'),
944                Some('t') => result.push('\t'),
945                Some(c) => {
946                    result.push('%');
947                    result.push(c);
948                }
949                None => result.push('%'),
950            }
951        } else {
952            result.push(ch);
953        }
954    }
955
956    result
957}
958
959/// Break down epoch seconds into date/time components
960fn breakdown_time(secs: i64) -> (i64, i64, i64, i64, i64, i64, i64, i64) {
961    const DAYS_IN_MONTH: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
962
963    fn is_leap_year(year: i64) -> bool {
964        (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
965    }
966
967    let sec = secs % 60;
968    let min = (secs / 60) % 60;
969    let hour = (secs / 3600) % 24;
970    let mut days = secs / 86400;
971
972    // wday: 0 = Sunday, 1970-01-01 was Thursday (4)
973    let wday = ((days + 4) % 7 + 7) % 7;
974
975    // Calculate year
976    let mut year = 1970i64;
977    loop {
978        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
979        if days >= days_in_year {
980            days -= days_in_year;
981            year += 1;
982        } else if days < 0 {
983            year -= 1;
984            let days_in_year = if is_leap_year(year) { 366 } else { 365 };
985            days += days_in_year;
986        } else {
987            break;
988        }
989    }
990
991    let yday = days + 1; // 1-based day of year
992
993    // Calculate month and day
994    let mut month = 1i64;
995    for (m, &dim) in DAYS_IN_MONTH.iter().enumerate() {
996        let mut days_in_month = dim;
997        if m == 1 && is_leap_year(year) {
998            days_in_month += 1;
999        }
1000        if days < days_in_month {
1001            month = m as i64 + 1;
1002            break;
1003        }
1004        days -= days_in_month;
1005    }
1006    let day = days + 1;
1007
1008    (year, month, day, hour, min, sec, wday, yday)
1009}