Skip to main content

oxilean_codegen/bash_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7use std::collections::{HashSet, VecDeque};
8
9/// Log level for bash script logging.
10#[allow(dead_code)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub enum BashLogLevel {
13    Debug,
14    Info,
15    Warn,
16    Error,
17    Fatal,
18}
19#[allow(dead_code)]
20impl BashLogLevel {
21    /// Returns the level name string.
22    pub fn name(&self) -> &'static str {
23        match self {
24            BashLogLevel::Debug => "DEBUG",
25            BashLogLevel::Info => "INFO",
26            BashLogLevel::Warn => "WARN",
27            BashLogLevel::Error => "ERROR",
28            BashLogLevel::Fatal => "FATAL",
29        }
30    }
31    /// Returns the ANSI color code for terminal output.
32    pub fn ansi_color(&self) -> &'static str {
33        match self {
34            BashLogLevel::Debug => "\\033[0;36m",
35            BashLogLevel::Info => "\\033[0;32m",
36            BashLogLevel::Warn => "\\033[0;33m",
37            BashLogLevel::Error => "\\033[0;31m",
38            BashLogLevel::Fatal => "\\033[1;31m",
39        }
40    }
41    /// Whether this level goes to stderr.
42    pub fn is_stderr(&self) -> bool {
43        matches!(self, BashLogLevel::Error | BashLogLevel::Fatal)
44    }
45}
46/// Bash logging framework generator.
47#[allow(dead_code)]
48#[derive(Debug, Clone)]
49pub struct BashLogger {
50    /// Whether to include timestamps.
51    pub timestamps: bool,
52    /// Whether to use ANSI color codes.
53    pub color: bool,
54    /// Minimum log level to emit.
55    pub min_level: BashLogLevel,
56    /// Log file path (if None, only stdout/stderr).
57    pub log_file: Option<std::string::String>,
58}
59#[allow(dead_code)]
60impl BashLogger {
61    /// Create a default logger (info level, no color, no timestamps).
62    pub fn new() -> Self {
63        BashLogger {
64            timestamps: false,
65            color: false,
66            min_level: BashLogLevel::Info,
67            log_file: None,
68        }
69    }
70    /// Enable timestamps.
71    pub fn with_timestamps(mut self) -> Self {
72        self.timestamps = true;
73        self
74    }
75    /// Enable ANSI color output.
76    pub fn with_color(mut self) -> Self {
77        self.color = true;
78        self
79    }
80    /// Set minimum log level.
81    pub fn with_min_level(mut self, level: BashLogLevel) -> Self {
82        self.min_level = level;
83        self
84    }
85    /// Set log file path.
86    pub fn with_log_file(mut self, path: &str) -> Self {
87        self.log_file = Some(path.to_string());
88        self
89    }
90    /// Emit the logging framework functions.
91    pub fn emit_framework(&self) -> std::string::String {
92        let mut out = std::string::String::new();
93        out.push_str("# ---- Logging framework ----\n");
94        out.push_str(&format!(
95            "readonly _LOG_LEVEL_MIN={}\n",
96            self.min_level as u8
97        ));
98        out.push_str("readonly _LOG_DEBUG=0 _LOG_INFO=1 _LOG_WARN=2 _LOG_ERROR=3 _LOG_FATAL=4\n");
99        out.push_str("_log() {\n");
100        out.push_str("  local level=$1 level_name=$2 message=$3\n");
101        out.push_str("  [[ $level -lt $_LOG_LEVEL_MIN ]] && return 0\n");
102        if self.timestamps {
103            out.push_str("  local ts; ts=$(date '+%Y-%m-%dT%H:%M:%S')\n");
104        }
105        let msg_prefix = if self.timestamps {
106            "\"[$ts][$level_name] $message\""
107        } else {
108            "\"[$level_name] $message\""
109        };
110        if self.color {
111            out.push_str("  local color reset='\\033[0m'\n");
112            out.push_str("  case $level_name in\n");
113            out.push_str("    DEBUG) color='\\033[0;36m' ;;\n");
114            out.push_str("    INFO)  color='\\033[0;32m' ;;\n");
115            out.push_str("    WARN)  color='\\033[0;33m' ;;\n");
116            out.push_str("    ERROR|FATAL) color='\\033[0;31m' ;;\n");
117            out.push_str("    *) color='' ;;\n");
118            out.push_str("  esac\n");
119            if self.timestamps {
120                out.push_str("  local formatted=\"${color}[$ts][$level_name]${reset} $message\"\n");
121            } else {
122                out.push_str("  local formatted=\"${color}[$level_name]${reset} $message\"\n");
123            }
124            out.push_str("  if [[ $level -ge $_LOG_ERROR ]]; then\n");
125            out.push_str("    echo -e \"$formatted\" >&2\n");
126            out.push_str("  else\n");
127            out.push_str("    echo -e \"$formatted\"\n");
128            out.push_str("  fi\n");
129        } else {
130            out.push_str(&format!("  if [[ $level -ge $_LOG_ERROR ]]; then\n"));
131            out.push_str(&format!("    echo {} >&2\n", msg_prefix));
132            out.push_str("  else\n");
133            out.push_str(&format!("    echo {}\n", msg_prefix));
134            out.push_str("  fi\n");
135        }
136        if let Some(log_file) = &self.log_file {
137            if self.timestamps {
138                out.push_str(&format!(
139                    "  echo \"[$ts][$level_name] $message\" >> \"{}\"\n",
140                    log_file
141                ));
142            } else {
143                out.push_str(&format!(
144                    "  echo \"[$level_name] $message\" >> \"{}\"\n",
145                    log_file
146                ));
147            }
148        }
149        out.push_str("}\n\n");
150        for (fn_name, level_const, level_name) in &[
151            ("log_debug", "_LOG_DEBUG", "DEBUG"),
152            ("log_info", "_LOG_INFO", "INFO"),
153            ("log_warn", "_LOG_WARN", "WARN"),
154            ("log_error", "_LOG_ERROR", "ERROR"),
155            ("log_fatal", "_LOG_FATAL", "FATAL"),
156        ] {
157            out.push_str(&format!(
158                "{fn_name}() {{ _log ${level_const} \"{level_name}\" \"$1\"; }}\n"
159            ));
160        }
161        out.push('\n');
162        out
163    }
164}
165#[allow(dead_code)]
166#[derive(Debug, Clone)]
167pub struct BashAnalysisCache {
168    pub(super) entries: std::collections::HashMap<String, BashCacheEntry>,
169    pub(super) max_size: usize,
170    pub(super) hits: u64,
171    pub(super) misses: u64,
172}
173impl BashAnalysisCache {
174    #[allow(dead_code)]
175    pub fn new(max_size: usize) -> Self {
176        BashAnalysisCache {
177            entries: std::collections::HashMap::new(),
178            max_size,
179            hits: 0,
180            misses: 0,
181        }
182    }
183    #[allow(dead_code)]
184    pub fn get(&mut self, key: &str) -> Option<&BashCacheEntry> {
185        if self.entries.contains_key(key) {
186            self.hits += 1;
187            self.entries.get(key)
188        } else {
189            self.misses += 1;
190            None
191        }
192    }
193    #[allow(dead_code)]
194    pub fn insert(&mut self, key: String, data: Vec<u8>) {
195        if self.entries.len() >= self.max_size {
196            if let Some(oldest) = self.entries.keys().next().cloned() {
197                self.entries.remove(&oldest);
198            }
199        }
200        self.entries.insert(
201            key.clone(),
202            BashCacheEntry {
203                key,
204                data,
205                timestamp: 0,
206                valid: true,
207            },
208        );
209    }
210    #[allow(dead_code)]
211    pub fn invalidate(&mut self, key: &str) {
212        if let Some(entry) = self.entries.get_mut(key) {
213            entry.valid = false;
214        }
215    }
216    #[allow(dead_code)]
217    pub fn clear(&mut self) {
218        self.entries.clear();
219    }
220    #[allow(dead_code)]
221    pub fn hit_rate(&self) -> f64 {
222        let total = self.hits + self.misses;
223        if total == 0 {
224            return 0.0;
225        }
226        self.hits as f64 / total as f64
227    }
228    #[allow(dead_code)]
229    pub fn size(&self) -> usize {
230        self.entries.len()
231    }
232}
233#[allow(dead_code)]
234pub struct BashConstantFoldingHelper;
235impl BashConstantFoldingHelper {
236    #[allow(dead_code)]
237    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
238        a.checked_add(b)
239    }
240    #[allow(dead_code)]
241    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
242        a.checked_sub(b)
243    }
244    #[allow(dead_code)]
245    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
246        a.checked_mul(b)
247    }
248    #[allow(dead_code)]
249    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
250        if b == 0 {
251            None
252        } else {
253            a.checked_div(b)
254        }
255    }
256    #[allow(dead_code)]
257    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
258        a + b
259    }
260    #[allow(dead_code)]
261    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
262        a * b
263    }
264    #[allow(dead_code)]
265    pub fn fold_neg_i64(a: i64) -> Option<i64> {
266        a.checked_neg()
267    }
268    #[allow(dead_code)]
269    pub fn fold_not_bool(a: bool) -> bool {
270        !a
271    }
272    #[allow(dead_code)]
273    pub fn fold_and_bool(a: bool, b: bool) -> bool {
274        a && b
275    }
276    #[allow(dead_code)]
277    pub fn fold_or_bool(a: bool, b: bool) -> bool {
278        a || b
279    }
280    #[allow(dead_code)]
281    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
282        a.checked_shl(b)
283    }
284    #[allow(dead_code)]
285    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
286        a.checked_shr(b)
287    }
288    #[allow(dead_code)]
289    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
290        if b == 0 {
291            None
292        } else {
293            Some(a % b)
294        }
295    }
296    #[allow(dead_code)]
297    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
298        a & b
299    }
300    #[allow(dead_code)]
301    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
302        a | b
303    }
304    #[allow(dead_code)]
305    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
306        a ^ b
307    }
308    #[allow(dead_code)]
309    pub fn fold_bitnot_i64(a: i64) -> i64 {
310        !a
311    }
312}
313/// A Bash here-document.
314#[derive(Debug, Clone, PartialEq)]
315pub struct BashHereDoc {
316    /// Delimiter (e.g. `EOF`)
317    pub delimiter: std::string::String,
318    /// Whether to suppress leading tab indentation (`<<-EOF`)
319    pub strip_tabs: bool,
320    /// Whether to prevent parameter expansion (`'EOF'`)
321    pub no_expand: bool,
322    /// Content lines
323    pub content: Vec<std::string::String>,
324}
325impl BashHereDoc {
326    /// Create a simple here-document.
327    pub fn new(
328        delimiter: impl Into<std::string::String>,
329        content: Vec<std::string::String>,
330    ) -> Self {
331        BashHereDoc {
332            delimiter: delimiter.into(),
333            strip_tabs: false,
334            no_expand: false,
335            content,
336        }
337    }
338}
339/// Bash variable kinds, classified by scope / export semantics.
340#[derive(Debug, Clone, PartialEq, Eq, Hash)]
341pub enum BashVar {
342    /// Regular local (function-scoped) variable: `local name`
343    Local(std::string::String),
344    /// Script-global variable: plain assignment at top level
345    Global(std::string::String),
346    /// Exported environment variable: `export NAME=...`
347    Env(std::string::String),
348    /// Read-only variable: `readonly NAME=...`
349    Readonly(std::string::String),
350    /// Integer variable: `declare -i NAME`
351    Integer(std::string::String),
352    /// Indexed array: `declare -a NAME`
353    Array(std::string::String),
354    /// Associative array: `declare -A NAME`
355    AssocArray(std::string::String),
356    /// Name-ref variable (Bash 4.3+): `declare -n NAME`
357    NameRef(std::string::String),
358}
359impl BashVar {
360    /// Get the variable name (without sigil).
361    pub fn name(&self) -> &str {
362        match self {
363            BashVar::Local(n)
364            | BashVar::Global(n)
365            | BashVar::Env(n)
366            | BashVar::Readonly(n)
367            | BashVar::Integer(n)
368            | BashVar::Array(n)
369            | BashVar::AssocArray(n)
370            | BashVar::NameRef(n) => n.as_str(),
371        }
372    }
373    /// Whether this variable is exported to the environment.
374    pub fn is_exported(&self) -> bool {
375        matches!(self, BashVar::Env(_))
376    }
377    /// Whether this variable is read-only.
378    pub fn is_readonly(&self) -> bool {
379        matches!(self, BashVar::Readonly(_))
380    }
381}
382/// A Bash `[[ ... ]]` conditional test.
383#[derive(Debug, Clone, PartialEq)]
384pub enum BashCondition {
385    /// `[[ -e file ]]`
386    FileExists(BashExpr),
387    /// `[[ -f file ]]`
388    IsFile(BashExpr),
389    /// `[[ -d file ]]`
390    IsDir(BashExpr),
391    /// `[[ -n str ]]`
392    NonEmpty(BashExpr),
393    /// `[[ -z str ]]`
394    Empty(BashExpr),
395    /// `[[ a == b ]]`
396    StrEq(BashExpr, BashExpr),
397    /// `[[ a != b ]]`
398    StrNe(BashExpr, BashExpr),
399    /// `[[ a < b ]]` (lexicographic)
400    StrLt(BashExpr, BashExpr),
401    /// `(( a < b ))`
402    ArithLt(std::string::String, std::string::String),
403    /// `(( a == b ))`
404    ArithEq(std::string::String, std::string::String),
405    /// `[[ cond1 && cond2 ]]`
406    And(Box<BashCondition>, Box<BashCondition>),
407    /// `[[ cond1 || cond2 ]]`
408    Or(Box<BashCondition>, Box<BashCondition>),
409    /// `! cond`
410    Not(Box<BashCondition>),
411    /// Raw condition string (fallback)
412    Raw(std::string::String),
413}
414#[allow(dead_code)]
415#[derive(Debug, Clone)]
416pub struct BashDepGraph {
417    pub(super) nodes: Vec<u32>,
418    pub(super) edges: Vec<(u32, u32)>,
419}
420impl BashDepGraph {
421    #[allow(dead_code)]
422    pub fn new() -> Self {
423        BashDepGraph {
424            nodes: Vec::new(),
425            edges: Vec::new(),
426        }
427    }
428    #[allow(dead_code)]
429    pub fn add_node(&mut self, id: u32) {
430        if !self.nodes.contains(&id) {
431            self.nodes.push(id);
432        }
433    }
434    #[allow(dead_code)]
435    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
436        self.add_node(dep);
437        self.add_node(dependent);
438        self.edges.push((dep, dependent));
439    }
440    #[allow(dead_code)]
441    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
442        self.edges
443            .iter()
444            .filter(|(d, _)| *d == node)
445            .map(|(_, dep)| *dep)
446            .collect()
447    }
448    #[allow(dead_code)]
449    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
450        self.edges
451            .iter()
452            .filter(|(_, dep)| *dep == node)
453            .map(|(d, _)| *d)
454            .collect()
455    }
456    #[allow(dead_code)]
457    pub fn topological_sort(&self) -> Vec<u32> {
458        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
459        for &n in &self.nodes {
460            in_degree.insert(n, 0);
461        }
462        for (_, dep) in &self.edges {
463            *in_degree.entry(*dep).or_insert(0) += 1;
464        }
465        let mut queue: std::collections::VecDeque<u32> = self
466            .nodes
467            .iter()
468            .filter(|&&n| in_degree[&n] == 0)
469            .copied()
470            .collect();
471        let mut result = Vec::new();
472        while let Some(node) = queue.pop_front() {
473            result.push(node);
474            for dep in self.dependents_of(node) {
475                let cnt = in_degree.entry(dep).or_insert(0);
476                *cnt = cnt.saturating_sub(1);
477                if *cnt == 0 {
478                    queue.push_back(dep);
479                }
480            }
481        }
482        result
483    }
484    #[allow(dead_code)]
485    pub fn has_cycle(&self) -> bool {
486        self.topological_sort().len() < self.nodes.len()
487    }
488}
489/// Background job tracker for bash scripts.
490#[allow(dead_code)]
491#[derive(Debug, Clone)]
492pub struct BashJobManager {
493    /// Maximum number of parallel jobs.
494    pub max_jobs: usize,
495    /// Whether to wait for all jobs on exit.
496    pub wait_on_exit: bool,
497    /// PID array variable name.
498    pub pids_var: std::string::String,
499}
500#[allow(dead_code)]
501impl BashJobManager {
502    /// Create a new job manager.
503    pub fn new(max_jobs: usize) -> Self {
504        BashJobManager {
505            max_jobs,
506            wait_on_exit: true,
507            pids_var: "PIDS".to_string(),
508        }
509    }
510    /// Set the PID array variable name.
511    pub fn with_pids_var(mut self, var: &str) -> Self {
512        self.pids_var = var.to_string();
513        self
514    }
515    /// Emit the job management framework.
516    pub fn emit_framework(&self) -> std::string::String {
517        let mut out = std::string::String::new();
518        out.push_str("# ---- Job manager ----\n");
519        out.push_str(&format!("declare -a {}\n", self.pids_var));
520        out.push_str(&format!("readonly _MAX_JOBS={}\n\n", self.max_jobs));
521        out.push_str("_wait_jobs() {\n");
522        out.push_str(&format!("  local -n _pids={}\n", self.pids_var));
523        out.push_str("  local _failed=0\n");
524        out.push_str("  for _pid in \"${_pids[@]:-}\"; do\n");
525        out.push_str("    wait \"$_pid\" || (( _failed++ ))\n");
526        out.push_str("  done\n");
527        out.push_str(&format!("  {}=()\n", self.pids_var));
528        out.push_str("  return $_failed\n");
529        out.push_str("}\n\n");
530        out.push_str("run_job() {\n");
531        out.push_str("  local _cmd=\"$@\"\n");
532        out.push_str(&format!("  local -n _pids={}\n", self.pids_var));
533        out.push_str("  while [[ ${#_pids[@]} -ge $_MAX_JOBS ]]; do\n");
534        out.push_str("    _wait_jobs || true\n");
535        out.push_str("  done\n");
536        out.push_str("  eval \"$_cmd\" &\n");
537        out.push_str("  _pids+=(\"$!\")\n");
538        out.push_str("}\n\n");
539        if self.wait_on_exit {
540            out.push_str("trap '_wait_jobs' EXIT\n\n");
541        }
542        out
543    }
544}
545#[allow(dead_code)]
546#[derive(Debug, Clone)]
547pub struct BashWorklist {
548    pub(super) items: std::collections::VecDeque<u32>,
549    pub(super) in_worklist: std::collections::HashSet<u32>,
550}
551impl BashWorklist {
552    #[allow(dead_code)]
553    pub fn new() -> Self {
554        BashWorklist {
555            items: std::collections::VecDeque::new(),
556            in_worklist: std::collections::HashSet::new(),
557        }
558    }
559    #[allow(dead_code)]
560    pub fn push(&mut self, item: u32) -> bool {
561        if self.in_worklist.insert(item) {
562            self.items.push_back(item);
563            true
564        } else {
565            false
566        }
567    }
568    #[allow(dead_code)]
569    pub fn pop(&mut self) -> Option<u32> {
570        let item = self.items.pop_front()?;
571        self.in_worklist.remove(&item);
572        Some(item)
573    }
574    #[allow(dead_code)]
575    pub fn is_empty(&self) -> bool {
576        self.items.is_empty()
577    }
578    #[allow(dead_code)]
579    pub fn len(&self) -> usize {
580        self.items.len()
581    }
582    #[allow(dead_code)]
583    pub fn contains(&self, item: u32) -> bool {
584        self.in_worklist.contains(&item)
585    }
586}
587/// A simple bash template with variable substitution.
588#[allow(dead_code)]
589#[derive(Debug, Clone)]
590pub struct BashTemplate {
591    /// Template text with `{{VAR}}` placeholders.
592    pub template: std::string::String,
593    /// Variable substitutions.
594    pub vars: HashMap<std::string::String, std::string::String>,
595}
596#[allow(dead_code)]
597impl BashTemplate {
598    /// Create a new template.
599    pub fn new(template: &str) -> Self {
600        BashTemplate {
601            template: template.to_string(),
602            vars: HashMap::new(),
603        }
604    }
605    /// Set a variable substitution.
606    pub fn set(mut self, key: &str, value: &str) -> Self {
607        self.vars.insert(key.to_string(), value.to_string());
608        self
609    }
610    /// Render the template.
611    pub fn render(&self) -> std::string::String {
612        let mut result = self.template.clone();
613        for (key, val) in &self.vars {
614            let placeholder = format!("{{{{{}}}}}", key);
615            result = result.replace(&placeholder, val);
616        }
617        result
618    }
619    /// Emit bash code that performs the substitution at runtime.
620    pub fn emit_bash_render(&self) -> std::string::String {
621        let mut out = std::string::String::new();
622        out.push_str("_render_template() {\n");
623        out.push_str("  local _template=\"$1\"\n");
624        for (key, _val) in &self.vars {
625            out.push_str(&format!(
626                "  _template=\"${{_template//\\\"{{{{{}}}}}\\\"/${{{}}}}}\"\n",
627                key,
628                key.to_uppercase()
629            ));
630        }
631        out.push_str("  echo \"$_template\"\n");
632        out.push_str("}\n");
633        out
634    }
635}
636/// A Bash statement (line or block).
637#[derive(Debug, Clone, PartialEq)]
638pub enum BashStatement {
639    /// Variable assignment: `name=value`
640    Assign(std::string::String, BashExpr),
641    /// Local variable declaration: `local name=value`
642    Local(std::string::String, Option<BashExpr>),
643    /// Exported variable: `export NAME=value`
644    Export(std::string::String, BashExpr),
645    /// Readonly variable: `readonly NAME=value`
646    Readonly(std::string::String, BashExpr),
647    /// Declare with flags: `declare -flags name=value`
648    Declare(std::string::String, std::string::String, Option<BashExpr>),
649    /// Command invocation (raw line)
650    Cmd(std::string::String),
651    /// If/elif/else block
652    If {
653        cond: BashCondition,
654        then: Vec<BashStatement>,
655        elifs: Vec<(BashCondition, Vec<BashStatement>)>,
656        else_: Option<Vec<BashStatement>>,
657    },
658    /// While loop
659    While {
660        cond: BashCondition,
661        body: Vec<BashStatement>,
662    },
663    /// For-in loop: `for var in list; do ... done`
664    For {
665        var: std::string::String,
666        in_: Vec<BashExpr>,
667        body: Vec<BashStatement>,
668    },
669    /// C-style for loop: `for (( init; cond; incr ))`
670    ForArith {
671        init: std::string::String,
672        cond: std::string::String,
673        incr: std::string::String,
674        body: Vec<BashStatement>,
675    },
676    /// `case` statement
677    Case {
678        expr: BashExpr,
679        arms: Vec<(std::string::String, Vec<BashStatement>)>,
680    },
681    /// Function call
682    Call(std::string::String, Vec<BashExpr>),
683    /// Return statement
684    Return(Option<u8>),
685    /// `break`
686    Break,
687    /// `continue`
688    Continue,
689    /// `echo` output
690    Echo(BashExpr),
691    /// `printf` formatted output
692    Printf(std::string::String, Vec<BashExpr>),
693    /// `read` input
694    Read(Vec<std::string::String>),
695    /// `exit` with code
696    Exit(u8),
697    /// Raw statement string (fallback)
698    Raw(std::string::String),
699    /// Pipe: cmd1 | cmd2
700    Pipe(Vec<std::string::String>),
701    /// Trap: `trap 'handler' SIGNAL`
702    Trap(std::string::String, std::string::String),
703    /// Source file: `. file` or `source file`
704    Source(std::string::String),
705}
706/// A bash heredoc block.
707#[allow(dead_code)]
708#[derive(Debug, Clone, PartialEq)]
709pub struct BashHeredoc {
710    /// The delimiter tag (e.g. "EOF", "SCRIPT").
711    pub tag: std::string::String,
712    /// Whether to use `<<-` (strip leading tabs) instead of `<<`.
713    pub strip_tabs: bool,
714    /// Whether to quote the tag (prevents expansion in body).
715    pub quoted: bool,
716    /// The heredoc body lines.
717    pub lines: Vec<std::string::String>,
718    /// Target file descriptor (default 1 = stdout).
719    pub fd: Option<u8>,
720}
721#[allow(dead_code)]
722impl BashHeredoc {
723    /// Create a new heredoc with a given delimiter tag.
724    pub fn new(tag: &str) -> Self {
725        BashHeredoc {
726            tag: tag.to_string(),
727            strip_tabs: false,
728            quoted: false,
729            lines: vec![],
730            fd: None,
731        }
732    }
733    /// Use `<<-` to strip leading tabs.
734    pub fn strip_tabs(mut self) -> Self {
735        self.strip_tabs = true;
736        self
737    }
738    /// Quote the delimiter (prevents variable expansion in body).
739    pub fn quoted(mut self) -> Self {
740        self.quoted = true;
741        self
742    }
743    /// Redirect to a specific file descriptor.
744    pub fn redirect_fd(mut self, fd: u8) -> Self {
745        self.fd = Some(fd);
746        self
747    }
748    /// Add a line to the heredoc body.
749    pub fn line(mut self, s: &str) -> Self {
750        self.lines.push(s.to_string());
751        self
752    }
753    /// Emit the heredoc as a string.
754    pub fn emit(&self) -> std::string::String {
755        let arrow = if self.strip_tabs { "<<-" } else { "<<" };
756        let tag = if self.quoted {
757            format!("'{}'", self.tag)
758        } else {
759            self.tag.clone()
760        };
761        let fd_str = self.fd.map(|fd| format!("{}&", fd)).unwrap_or_default();
762        let mut out = format!("{}{}{}\\n", fd_str, arrow, tag);
763        for l in &self.lines {
764            out.push_str(l);
765            out.push_str("\\n");
766        }
767        out.push_str(&self.tag);
768        out
769    }
770}
771/// A generated bash argument parser.
772#[allow(dead_code)]
773#[derive(Debug, Clone)]
774pub struct BashArgParser {
775    /// Program name for usage display.
776    pub prog_name: std::string::String,
777    /// Options to parse.
778    pub options: Vec<BashCliOption>,
779    /// Positional argument names.
780    pub positionals: Vec<std::string::String>,
781    /// Usage description.
782    pub description: std::string::String,
783}
784#[allow(dead_code)]
785impl BashArgParser {
786    /// Create a new argument parser.
787    pub fn new(prog_name: &str, description: &str) -> Self {
788        BashArgParser {
789            prog_name: prog_name.to_string(),
790            options: vec![],
791            positionals: vec![],
792            description: description.to_string(),
793        }
794    }
795    /// Add an option.
796    pub fn add_option(mut self, opt: BashCliOption) -> Self {
797        self.options.push(opt);
798        self
799    }
800    /// Add a positional argument name.
801    pub fn add_positional(mut self, name: &str) -> Self {
802        self.positionals.push(name.to_string());
803        self
804    }
805    /// Emit the usage function.
806    pub fn emit_usage(&self) -> std::string::String {
807        let mut out = std::string::String::new();
808        out.push_str("usage() {\n");
809        out.push_str(&format!("  echo \"Usage: {} [OPTIONS]", self.prog_name));
810        for pos in &self.positionals {
811            out.push_str(&format!(" <{}>", pos));
812        }
813        out.push_str("\"\n");
814        out.push_str(&format!("  echo \"{}\"\n", self.description));
815        out.push_str("  echo \"\"\n");
816        out.push_str("  echo \"Options:\"\n");
817        out.push_str("  echo \"  -h, --help      Show this help message\"\n");
818        for opt in &self.options {
819            let short_str = opt
820                .short
821                .map(|c| format!("-{}, ", c))
822                .unwrap_or_else(|| "    ".to_string());
823            let arg_meta = if opt.has_arg { " <VALUE>" } else { "" };
824            let req_str = if opt.required { " (required)" } else { "" };
825            let def_str = opt
826                .default
827                .as_ref()
828                .map(|d| format!(" [default: {}]", d))
829                .unwrap_or_default();
830            out.push_str(&format!(
831                "  {}--{}{:<12}{}{}{}\n",
832                short_str, opt.long, arg_meta, opt.help, req_str, def_str
833            ));
834        }
835        out.push_str("}\n");
836        out
837    }
838    /// Emit the full argument parsing block.
839    pub fn emit_parse_block(&self) -> std::string::String {
840        let mut out = std::string::String::new();
841        for opt in &self.options {
842            if let Some(default) = &opt.default {
843                out.push_str(&format!("{}={}\n", opt.var_name.to_uppercase(), default));
844            }
845        }
846        out.push('\n');
847        out.push_str("while [[ $# -gt 0 ]]; do\n");
848        out.push_str("  case \"$1\" in\n");
849        out.push_str("    -h|--help) usage; exit 0 ;;\n");
850        for opt in &self.options {
851            let short_pat = opt.short.map(|c| format!("-{}|", c)).unwrap_or_default();
852            if opt.has_arg {
853                out.push_str(&format!(
854                    "    {}--{})\n      {}=\"${{2:-}}\"\n      shift 2\n      ;;\n",
855                    short_pat,
856                    opt.long,
857                    opt.var_name.to_uppercase()
858                ));
859            } else {
860                out.push_str(&format!(
861                    "    {}--{}) {}=true; shift ;;\n",
862                    short_pat,
863                    opt.long,
864                    opt.var_name.to_uppercase()
865                ));
866            }
867        }
868        out.push_str("    --) shift; break ;;\n");
869        out.push_str("    -*) echo \"Unknown option: $1\" >&2; usage; exit 1 ;;\n");
870        out.push_str("    *) break ;;\n");
871        out.push_str("  esac\n");
872        out.push_str("done\n\n");
873        for opt in &self.options {
874            if opt.required {
875                out.push_str(
876                    &format!(
877                        "if [[ -z \"${{{}:-}}\" ]]; then\n  echo \"Error: {} is required\" >&2\n  usage\n  exit 1\nfi\n",
878                        opt.var_name.to_uppercase(), opt.var_name.to_uppercase()
879                    ),
880                );
881            }
882        }
883        for (i, pos) in self.positionals.iter().enumerate() {
884            out.push_str(&format!(
885                "readonly {}=\"${{{}:-}}\"\n",
886                pos.to_uppercase(),
887                i + 1
888            ));
889        }
890        out
891    }
892}
893/// A command-line option for the generated argument parser.
894#[allow(dead_code)]
895#[derive(Debug, Clone)]
896pub struct BashCliOption {
897    /// Long option name (e.g. "verbose").
898    pub long: std::string::String,
899    /// Short option character (e.g. 'v'), if any.
900    pub short: Option<char>,
901    /// Variable name to store the value in.
902    pub var_name: std::string::String,
903    /// Default value (empty string means no default).
904    pub default: Option<std::string::String>,
905    /// Whether the option takes an argument.
906    pub has_arg: bool,
907    /// Help text.
908    pub help: std::string::String,
909    /// Whether the option is required.
910    pub required: bool,
911}
912#[allow(dead_code)]
913impl BashCliOption {
914    /// Create a boolean flag (no argument).
915    pub fn flag(long: &str, short: Option<char>, var_name: &str, help: &str) -> Self {
916        BashCliOption {
917            long: long.to_string(),
918            short,
919            var_name: var_name.to_string(),
920            default: Some("false".to_string()),
921            has_arg: false,
922            help: help.to_string(),
923            required: false,
924        }
925    }
926    /// Create an option that takes an argument.
927    pub fn arg(
928        long: &str,
929        short: Option<char>,
930        var_name: &str,
931        default: Option<&str>,
932        help: &str,
933    ) -> Self {
934        BashCliOption {
935            long: long.to_string(),
936            short,
937            var_name: var_name.to_string(),
938            default: default.map(|s| s.to_string()),
939            has_arg: true,
940            help: help.to_string(),
941            required: false,
942        }
943    }
944    /// Mark this option as required.
945    pub fn required(mut self) -> Self {
946        self.required = true;
947        self
948    }
949}
950#[allow(dead_code)]
951#[derive(Debug, Clone, Default)]
952pub struct BashPassStats {
953    pub total_runs: u32,
954    pub successful_runs: u32,
955    pub total_changes: u64,
956    pub time_ms: u64,
957    pub iterations_used: u32,
958}
959impl BashPassStats {
960    #[allow(dead_code)]
961    pub fn new() -> Self {
962        Self::default()
963    }
964    #[allow(dead_code)]
965    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
966        self.total_runs += 1;
967        self.successful_runs += 1;
968        self.total_changes += changes;
969        self.time_ms += time_ms;
970        self.iterations_used = iterations;
971    }
972    #[allow(dead_code)]
973    pub fn average_changes_per_run(&self) -> f64 {
974        if self.total_runs == 0 {
975            return 0.0;
976        }
977        self.total_changes as f64 / self.total_runs as f64
978    }
979    #[allow(dead_code)]
980    pub fn success_rate(&self) -> f64 {
981        if self.total_runs == 0 {
982            return 0.0;
983        }
984        self.successful_runs as f64 / self.total_runs as f64
985    }
986    #[allow(dead_code)]
987    pub fn format_summary(&self) -> String {
988        format!(
989            "Runs: {}/{}, Changes: {}, Time: {}ms",
990            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
991        )
992    }
993}
994#[allow(dead_code)]
995#[derive(Debug, Clone)]
996pub struct BashDominatorTree {
997    pub idom: Vec<Option<u32>>,
998    pub dom_children: Vec<Vec<u32>>,
999    pub dom_depth: Vec<u32>,
1000}
1001impl BashDominatorTree {
1002    #[allow(dead_code)]
1003    pub fn new(size: usize) -> Self {
1004        BashDominatorTree {
1005            idom: vec![None; size],
1006            dom_children: vec![Vec::new(); size],
1007            dom_depth: vec![0; size],
1008        }
1009    }
1010    #[allow(dead_code)]
1011    pub fn set_idom(&mut self, node: usize, idom: u32) {
1012        self.idom[node] = Some(idom);
1013    }
1014    #[allow(dead_code)]
1015    pub fn dominates(&self, a: usize, b: usize) -> bool {
1016        if a == b {
1017            return true;
1018        }
1019        let mut cur = b;
1020        loop {
1021            match self.idom[cur] {
1022                Some(parent) if parent as usize == a => return true,
1023                Some(parent) if parent as usize == cur => return false,
1024                Some(parent) => cur = parent as usize,
1025                None => return false,
1026            }
1027        }
1028    }
1029    #[allow(dead_code)]
1030    pub fn depth(&self, node: usize) -> u32 {
1031        self.dom_depth.get(node).copied().unwrap_or(0)
1032    }
1033}
1034/// Bash expression / expansion node.
1035#[derive(Debug, Clone, PartialEq)]
1036pub enum BashExpr {
1037    /// Variable expansion: `${name}` or `$name`
1038    Var(BashVar),
1039    /// String literal (single-quoted, no expansion): `'hello'`
1040    Lit(std::string::String),
1041    /// Double-quoted string (allows expansions): `"hello $world"`
1042    DQuoted(std::string::String),
1043    /// Command substitution: `$(cmd)`
1044    CmdSubst(std::string::String),
1045    /// Arithmetic expansion: `$(( expr ))`
1046    ArithExpr(std::string::String),
1047    /// Process substitution: `<(cmd)` or `>(cmd)`
1048    ProcSubst {
1049        is_input: bool,
1050        cmd: std::string::String,
1051    },
1052    /// Array element: `${arr[idx]}`
1053    ArrayElem(std::string::String, Box<BashExpr>),
1054    /// Array length: `${#arr[@]}`
1055    ArrayLen(std::string::String),
1056    /// All array elements: `"${arr[@]}"`
1057    ArrayAll(std::string::String),
1058    /// Associative array element: `${map[$key]}`
1059    AssocElem(std::string::String, Box<BashExpr>),
1060    /// Parameter expansion with default: `${var:-default}`
1061    Default(std::string::String, Box<BashExpr>),
1062    /// Parameter expansion with assign default: `${var:=default}`
1063    AssignDefault(std::string::String, Box<BashExpr>),
1064    /// Substring: `${var:offset:length}`
1065    Substring(std::string::String, usize, Option<usize>),
1066    /// String length: `${#var}`
1067    StringLen(std::string::String),
1068    /// Pattern removal (prefix): `${var#pattern}`
1069    StripPrefix(std::string::String, std::string::String),
1070    /// Pattern removal (suffix): `${var%pattern}`
1071    StripSuffix(std::string::String, std::string::String),
1072    /// Case conversion (Bash 4+): `${var^^}` / `${var,,}`
1073    UpperCase(std::string::String),
1074    LowerCase(std::string::String),
1075    /// Exit code of last command: `$?`
1076    LastStatus,
1077    /// PID of current shell: `$$`
1078    ShellPid,
1079    /// Script name: `$0`
1080    ScriptName,
1081    /// Positional argument: `$N`
1082    Positional(usize),
1083    /// All positional arguments: `"$@"`
1084    AllArgs,
1085    /// Number of positional arguments: `$#`
1086    ArgCount,
1087    /// Concatenation of two expressions
1088    Concat(Box<BashExpr>, Box<BashExpr>),
1089}
1090#[allow(dead_code)]
1091#[derive(Debug, Clone, PartialEq)]
1092pub enum BashPassPhase {
1093    Analysis,
1094    Transformation,
1095    Verification,
1096    Cleanup,
1097}
1098impl BashPassPhase {
1099    #[allow(dead_code)]
1100    pub fn name(&self) -> &str {
1101        match self {
1102            BashPassPhase::Analysis => "analysis",
1103            BashPassPhase::Transformation => "transformation",
1104            BashPassPhase::Verification => "verification",
1105            BashPassPhase::Cleanup => "cleanup",
1106        }
1107    }
1108    #[allow(dead_code)]
1109    pub fn is_modifying(&self) -> bool {
1110        matches!(self, BashPassPhase::Transformation | BashPassPhase::Cleanup)
1111    }
1112}
1113/// A complete Bash script.
1114#[derive(Debug, Clone)]
1115pub struct BashScript {
1116    /// Shebang line (e.g. `#!/usr/bin/env bash`)
1117    pub shebang: std::string::String,
1118    /// Initial set flags (e.g. `set -euo pipefail`)
1119    pub set_flags: Vec<std::string::String>,
1120    /// Trap handlers: `(signal, handler)`
1121    pub traps: Vec<(std::string::String, std::string::String)>,
1122    /// Top-level variable declarations
1123    pub globals: Vec<(std::string::String, std::string::String)>,
1124    /// Helper functions
1125    pub functions: Vec<BashFunction>,
1126    /// Main body statements (raw lines)
1127    pub main: Vec<std::string::String>,
1128}
1129impl BashScript {
1130    /// Create a new Bash script with a standard shebang.
1131    pub fn new() -> Self {
1132        BashScript {
1133            shebang: "#!/usr/bin/env bash".to_string(),
1134            set_flags: vec!["-euo".to_string(), "pipefail".to_string()],
1135            traps: vec![],
1136            globals: vec![],
1137            functions: vec![],
1138            main: vec![],
1139        }
1140    }
1141    /// Create a script with strict mode disabled.
1142    pub fn lenient() -> Self {
1143        BashScript {
1144            shebang: "#!/usr/bin/env bash".to_string(),
1145            set_flags: vec![],
1146            traps: vec![],
1147            globals: vec![],
1148            functions: vec![],
1149            main: vec![],
1150        }
1151    }
1152}
1153#[allow(dead_code)]
1154#[derive(Debug, Clone)]
1155pub struct BashPassConfig {
1156    pub phase: BashPassPhase,
1157    pub enabled: bool,
1158    pub max_iterations: u32,
1159    pub debug_output: bool,
1160    pub pass_name: String,
1161}
1162impl BashPassConfig {
1163    #[allow(dead_code)]
1164    pub fn new(name: impl Into<String>, phase: BashPassPhase) -> Self {
1165        BashPassConfig {
1166            phase,
1167            enabled: true,
1168            max_iterations: 10,
1169            debug_output: false,
1170            pass_name: name.into(),
1171        }
1172    }
1173    #[allow(dead_code)]
1174    pub fn disabled(mut self) -> Self {
1175        self.enabled = false;
1176        self
1177    }
1178    #[allow(dead_code)]
1179    pub fn with_debug(mut self) -> Self {
1180        self.debug_output = true;
1181        self
1182    }
1183    #[allow(dead_code)]
1184    pub fn max_iter(mut self, n: u32) -> Self {
1185        self.max_iterations = n;
1186        self
1187    }
1188}
1189#[allow(dead_code)]
1190pub struct BashPassRegistry {
1191    pub(super) configs: Vec<BashPassConfig>,
1192    pub(super) stats: std::collections::HashMap<String, BashPassStats>,
1193}
1194impl BashPassRegistry {
1195    #[allow(dead_code)]
1196    pub fn new() -> Self {
1197        BashPassRegistry {
1198            configs: Vec::new(),
1199            stats: std::collections::HashMap::new(),
1200        }
1201    }
1202    #[allow(dead_code)]
1203    pub fn register(&mut self, config: BashPassConfig) {
1204        self.stats
1205            .insert(config.pass_name.clone(), BashPassStats::new());
1206        self.configs.push(config);
1207    }
1208    #[allow(dead_code)]
1209    pub fn enabled_passes(&self) -> Vec<&BashPassConfig> {
1210        self.configs.iter().filter(|c| c.enabled).collect()
1211    }
1212    #[allow(dead_code)]
1213    pub fn get_stats(&self, name: &str) -> Option<&BashPassStats> {
1214        self.stats.get(name)
1215    }
1216    #[allow(dead_code)]
1217    pub fn total_passes(&self) -> usize {
1218        self.configs.len()
1219    }
1220    #[allow(dead_code)]
1221    pub fn enabled_count(&self) -> usize {
1222        self.enabled_passes().len()
1223    }
1224    #[allow(dead_code)]
1225    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
1226        if let Some(stats) = self.stats.get_mut(name) {
1227            stats.record_run(changes, time_ms, iter);
1228        }
1229    }
1230}
1231#[allow(dead_code)]
1232#[derive(Debug, Clone)]
1233pub struct BashCacheEntry {
1234    pub key: String,
1235    pub data: Vec<u8>,
1236    pub timestamp: u64,
1237    pub valid: bool,
1238}
1239/// A Bash shell function.
1240#[derive(Debug, Clone, PartialEq)]
1241pub struct BashFunction {
1242    /// Function name
1243    pub name: std::string::String,
1244    /// Local variable declarations (names only — values set in body)
1245    pub local_vars: Vec<std::string::String>,
1246    /// Body lines (raw Bash statements)
1247    pub body: Vec<std::string::String>,
1248    /// Optional description (emitted as a comment before the function)
1249    pub description: Option<std::string::String>,
1250}
1251impl BashFunction {
1252    /// Create a new function with a name and body lines.
1253    pub fn new(name: impl Into<std::string::String>, body: Vec<std::string::String>) -> Self {
1254        BashFunction {
1255            name: name.into(),
1256            local_vars: vec![],
1257            body,
1258            description: None,
1259        }
1260    }
1261    /// Create a function with local variables.
1262    pub fn with_locals(
1263        name: impl Into<std::string::String>,
1264        local_vars: Vec<std::string::String>,
1265        body: Vec<std::string::String>,
1266    ) -> Self {
1267        BashFunction {
1268            name: name.into(),
1269            local_vars,
1270            body,
1271            description: None,
1272        }
1273    }
1274}
1275/// Bash script code generation backend for OxiLean.
1276pub struct BashBackend {
1277    /// Indent string (default: 4 spaces)
1278    pub(super) indent: std::string::String,
1279    /// Name mangling cache
1280    pub(super) mangle_cache: HashMap<std::string::String, std::string::String>,
1281    /// Whether to add blank lines between functions
1282    pub(super) spacing: bool,
1283}
1284impl BashBackend {
1285    /// Create a new BashBackend with default settings.
1286    pub fn new() -> Self {
1287        BashBackend {
1288            indent: "    ".to_string(),
1289            mangle_cache: HashMap::new(),
1290            spacing: true,
1291        }
1292    }
1293    /// Create a BashBackend with 2-space indentation.
1294    pub fn compact() -> Self {
1295        BashBackend {
1296            indent: "  ".to_string(),
1297            mangle_cache: HashMap::new(),
1298            spacing: false,
1299        }
1300    }
1301    /// Emit a BashVar as its declaration line (for `declare` statements).
1302    pub fn emit_var_decl(&self, var: &BashVar, value: Option<&str>) -> std::string::String {
1303        let val_s = value.map(|v| format!("={}", v)).unwrap_or_default();
1304        match var {
1305            BashVar::Local(n) => format!("local {}{}", n, val_s),
1306            BashVar::Global(n) => format!("{}{}", n, val_s),
1307            BashVar::Env(n) => format!("export {}{}", n, val_s),
1308            BashVar::Readonly(n) => format!("readonly {}{}", n, val_s),
1309            BashVar::Integer(n) => format!("declare -i {}{}", n, val_s),
1310            BashVar::Array(n) => format!("declare -a {}{}", n, val_s),
1311            BashVar::AssocArray(n) => format!("declare -A {}{}", n, val_s),
1312            BashVar::NameRef(n) => format!("declare -n {}{}", n, val_s),
1313        }
1314    }
1315    /// Emit a BashVar as an expansion (`${name}` form).
1316    pub fn emit_var(&self, var: &BashVar) -> std::string::String {
1317        format!("{}", var)
1318    }
1319    /// Emit a BashExpr as a string.
1320    pub fn emit_expr(&self, expr: &BashExpr) -> std::string::String {
1321        format!("{}", expr)
1322    }
1323    /// Emit a BashCondition as a string.
1324    pub fn emit_condition(&self, cond: &BashCondition) -> std::string::String {
1325        format!("{}", cond)
1326    }
1327    /// Mangle an OxiLean name into a valid Bash identifier.
1328    ///
1329    /// Bash function/variable names match `[a-zA-Z_][a-zA-Z0-9_]*`.
1330    /// Namespace separators (`.`, `::`) become `__`.
1331    pub fn mangle_name(&self, name: &str) -> std::string::String {
1332        if let Some(cached) = self.mangle_cache.get(name) {
1333            return cached.clone();
1334        }
1335        let mut result = std::string::String::new();
1336        let mut prev_special = false;
1337        for (i, c) in name.chars().enumerate() {
1338            match c {
1339                'a'..='z' | 'A'..='Z' | '_' => {
1340                    result.push(c);
1341                    prev_special = false;
1342                }
1343                '0'..='9' => {
1344                    if i == 0 {
1345                        result.push('_');
1346                    }
1347                    result.push(c);
1348                    prev_special = false;
1349                }
1350                '.' | ':' => {
1351                    if !prev_special {
1352                        result.push_str("__");
1353                    }
1354                    prev_special = true;
1355                }
1356                '\'' | '-' => {
1357                    if !prev_special {
1358                        result.push('_');
1359                    }
1360                    prev_special = true;
1361                }
1362                _ => {
1363                    if !prev_special {
1364                        result.push_str(&format!("_u{:04X}_", c as u32));
1365                    }
1366                    prev_special = true;
1367                }
1368            }
1369        }
1370        if result.is_empty() {
1371            result.push('_');
1372        }
1373        let builtins = [
1374            "alias",
1375            "bg",
1376            "bind",
1377            "break",
1378            "builtin",
1379            "caller",
1380            "cd",
1381            "command",
1382            "compgen",
1383            "complete",
1384            "compopt",
1385            "continue",
1386            "declare",
1387            "dirs",
1388            "disown",
1389            "echo",
1390            "enable",
1391            "eval",
1392            "exec",
1393            "exit",
1394            "export",
1395            "false",
1396            "fc",
1397            "fg",
1398            "getopts",
1399            "hash",
1400            "help",
1401            "history",
1402            "if",
1403            "jobs",
1404            "kill",
1405            "let",
1406            "local",
1407            "logout",
1408            "mapfile",
1409            "popd",
1410            "printf",
1411            "pushd",
1412            "pwd",
1413            "read",
1414            "readarray",
1415            "readonly",
1416            "return",
1417            "select",
1418            "set",
1419            "shift",
1420            "shopt",
1421            "source",
1422            "suspend",
1423            "test",
1424            "time",
1425            "times",
1426            "trap",
1427            "true",
1428            "type",
1429            "typeset",
1430            "ulimit",
1431            "umask",
1432            "unalias",
1433            "unset",
1434            "until",
1435            "wait",
1436            "while",
1437        ];
1438        if builtins.contains(&result.as_str()) {
1439            result.push_str("__ox");
1440        }
1441        result
1442    }
1443    /// Emit a BashFunction as a shell function definition.
1444    pub fn emit_function(&self, func: &BashFunction) -> std::string::String {
1445        let mut out = std::string::String::new();
1446        if let Some(desc) = &func.description {
1447            out.push_str(&format!("# {}\n", desc));
1448        }
1449        out.push_str(&format!("{}() {{\n", func.name));
1450        for local in &func.local_vars {
1451            out.push_str(&format!("{}local {}\n", self.indent, local));
1452        }
1453        if !func.local_vars.is_empty() && !func.body.is_empty() {
1454            out.push('\n');
1455        }
1456        for line in &func.body {
1457            if line.is_empty() {
1458                out.push('\n');
1459            } else {
1460                out.push_str(&format!("{}{}\n", self.indent, line));
1461            }
1462        }
1463        out.push_str("}\n");
1464        out
1465    }
1466    /// Emit a BashHereDoc as a string.
1467    pub fn emit_heredoc(&self, heredoc: &BashHereDoc) -> std::string::String {
1468        format!("{}", heredoc)
1469    }
1470    /// Emit a complete BashScript as a string.
1471    pub fn emit_script(&self, script: &BashScript) -> std::string::String {
1472        let mut out = std::string::String::new();
1473        out.push_str(&format!("{}\n", script.shebang));
1474        if !script.set_flags.is_empty() {
1475            out.push_str(&format!("set {}\n", script.set_flags.join(" ")));
1476        }
1477        out.push('\n');
1478        for (signal, handler) in &script.traps {
1479            out.push_str(&format!("trap '{}' {}\n", handler, signal));
1480        }
1481        if !script.traps.is_empty() {
1482            out.push('\n');
1483        }
1484        for (name, value) in &script.globals {
1485            out.push_str(&format!("readonly {}={}\n", name, value));
1486        }
1487        if !script.globals.is_empty() {
1488            out.push('\n');
1489        }
1490        for func in &script.functions {
1491            out.push_str(&self.emit_function(func));
1492            if self.spacing {
1493                out.push('\n');
1494            }
1495        }
1496        if !script.main.is_empty() {
1497            if !script.functions.is_empty() {
1498                out.push_str("# --- main ---\n");
1499            }
1500            for line in &script.main {
1501                out.push_str(line);
1502                out.push('\n');
1503            }
1504        }
1505        out
1506    }
1507    /// Emit an array assignment: `name=(elem1 elem2 ...)`
1508    pub fn emit_array_assign(&self, name: &str, elems: &[BashExpr]) -> std::string::String {
1509        let elems_s: Vec<std::string::String> = elems.iter().map(|e| format!("{}", e)).collect();
1510        format!("{}=({})", name, elems_s.join(" "))
1511    }
1512    /// Emit an associative array assignment block.
1513    pub fn emit_assoc_array_assign(
1514        &self,
1515        name: &str,
1516        pairs: &[(std::string::String, std::string::String)],
1517    ) -> std::string::String {
1518        let mut out = format!("declare -A {}\n", name);
1519        for (k, v) in pairs {
1520            out.push_str(&format!("{}[{}]={}\n", name, k, v));
1521        }
1522        out
1523    }
1524    /// Emit an if statement.
1525    pub fn emit_if(
1526        &self,
1527        cond: &BashCondition,
1528        then: &[&str],
1529        else_: Option<&[&str]>,
1530    ) -> std::string::String {
1531        let mut out = format!("if {}; then\n", cond);
1532        for line in then {
1533            out.push_str(&format!("{}{}\n", self.indent, line));
1534        }
1535        if let Some(else_body) = else_ {
1536            out.push_str("else\n");
1537            for line in else_body {
1538                out.push_str(&format!("{}{}\n", self.indent, line));
1539            }
1540        }
1541        out.push_str("fi\n");
1542        out
1543    }
1544    /// Emit a for-in loop.
1545    pub fn emit_for_in(&self, var: &str, items: &[BashExpr], body: &[&str]) -> std::string::String {
1546        let items_s: Vec<std::string::String> = items.iter().map(|e| format!("{}", e)).collect();
1547        let mut out = format!("for {} in {}; do\n", var, items_s.join(" "));
1548        for line in body {
1549            out.push_str(&format!("{}{}\n", self.indent, line));
1550        }
1551        out.push_str("done\n");
1552        out
1553    }
1554    /// Emit a while loop.
1555    pub fn emit_while(&self, cond: &BashCondition, body: &[&str]) -> std::string::String {
1556        let mut out = format!("while {}; do\n", cond);
1557        for line in body {
1558            out.push_str(&format!("{}{}\n", self.indent, line));
1559        }
1560        out.push_str("done\n");
1561        out
1562    }
1563    /// Emit a case statement.
1564    pub fn emit_case(&self, expr: &BashExpr, arms: &[(&str, Vec<&str>)]) -> std::string::String {
1565        let mut out = format!("case {} in\n", expr);
1566        for (pattern, body) in arms {
1567            out.push_str(&format!("{}{})\n", self.indent, pattern));
1568            for line in body {
1569                out.push_str(&format!("{}{}{}\n", self.indent, self.indent, line));
1570            }
1571            out.push_str(&format!("{};;\n", self.indent));
1572        }
1573        out.push_str("esac\n");
1574        out
1575    }
1576}
1577#[allow(dead_code)]
1578#[derive(Debug, Clone)]
1579pub struct BashLivenessInfo {
1580    pub live_in: Vec<std::collections::HashSet<u32>>,
1581    pub live_out: Vec<std::collections::HashSet<u32>>,
1582    pub defs: Vec<std::collections::HashSet<u32>>,
1583    pub uses: Vec<std::collections::HashSet<u32>>,
1584}
1585impl BashLivenessInfo {
1586    #[allow(dead_code)]
1587    pub fn new(block_count: usize) -> Self {
1588        BashLivenessInfo {
1589            live_in: vec![std::collections::HashSet::new(); block_count],
1590            live_out: vec![std::collections::HashSet::new(); block_count],
1591            defs: vec![std::collections::HashSet::new(); block_count],
1592            uses: vec![std::collections::HashSet::new(); block_count],
1593        }
1594    }
1595    #[allow(dead_code)]
1596    pub fn add_def(&mut self, block: usize, var: u32) {
1597        if block < self.defs.len() {
1598            self.defs[block].insert(var);
1599        }
1600    }
1601    #[allow(dead_code)]
1602    pub fn add_use(&mut self, block: usize, var: u32) {
1603        if block < self.uses.len() {
1604            self.uses[block].insert(var);
1605        }
1606    }
1607    #[allow(dead_code)]
1608    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1609        self.live_in
1610            .get(block)
1611            .map(|s| s.contains(&var))
1612            .unwrap_or(false)
1613    }
1614    #[allow(dead_code)]
1615    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1616        self.live_out
1617            .get(block)
1618            .map(|s| s.contains(&var))
1619            .unwrap_or(false)
1620    }
1621}
1622/// A signal trap specification.
1623#[allow(dead_code)]
1624#[derive(Debug, Clone, PartialEq)]
1625pub struct BashTrap {
1626    /// Signal name (EXIT, ERR, INT, TERM, HUP, etc.).
1627    pub signal: std::string::String,
1628    /// Handler command or function name.
1629    pub handler: std::string::String,
1630    /// Whether to reset to default (use `-` as handler).
1631    pub reset: bool,
1632}
1633#[allow(dead_code)]
1634impl BashTrap {
1635    /// Create a new trap for a signal.
1636    pub fn new(signal: &str, handler: &str) -> Self {
1637        BashTrap {
1638            signal: signal.to_string(),
1639            handler: handler.to_string(),
1640            reset: false,
1641        }
1642    }
1643    /// Create an EXIT trap.
1644    pub fn on_exit(handler: &str) -> Self {
1645        BashTrap::new("EXIT", handler)
1646    }
1647    /// Create an ERR trap.
1648    pub fn on_err(handler: &str) -> Self {
1649        BashTrap::new("ERR", handler)
1650    }
1651    /// Create an INT trap (Ctrl-C).
1652    pub fn on_int(handler: &str) -> Self {
1653        BashTrap::new("INT", handler)
1654    }
1655    /// Create a TERM trap.
1656    pub fn on_term(handler: &str) -> Self {
1657        BashTrap::new("TERM", handler)
1658    }
1659    /// Create a reset trap (ignore the signal).
1660    pub fn reset(signal: &str) -> Self {
1661        BashTrap {
1662            signal: signal.to_string(),
1663            handler: "-".to_string(),
1664            reset: true,
1665        }
1666    }
1667    /// Emit the trap statement.
1668    pub fn emit(&self) -> std::string::String {
1669        format!("trap '{}' {}", self.handler, self.signal)
1670    }
1671}