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