Skip to main content

oxilean_codegen/haskell_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6
7use super::functions::*;
8use std::collections::{HashMap, HashSet, VecDeque};
9
10/// The various top-level declarations in a Haskell module.
11#[derive(Debug, Clone, PartialEq)]
12pub enum HaskellDecl {
13    Data(HaskellDataDecl),
14    Newtype(HaskellNewtype),
15    TypeClass(HaskellTypeClass),
16    Instance(HaskellInstance),
17    Function(HaskellFunction),
18    TypeSynonym(String, Vec<String>, HaskellType),
19    Comment(String),
20    RawLine(String),
21}
22/// A Haskell `data` declaration.
23///
24/// Example: `data Expr = Lit Int | Add Expr Expr deriving (Show, Eq)`
25#[derive(Debug, Clone, PartialEq)]
26pub struct HaskellDataDecl {
27    /// Type name: `Expr`
28    pub name: String,
29    /// Type parameters: `a`, `b`, ...
30    pub type_params: Vec<String>,
31    /// Constructor list: (constructor_name, field_types)
32    pub constructors: Vec<(String, Vec<HaskellType>)>,
33    /// Deriving clauses: `Show`, `Eq`, `Ord`, ...
34    pub deriving_clauses: Vec<String>,
35}
36/// A single alternative in a `case` expression.
37#[derive(Debug, Clone, PartialEq)]
38pub struct HaskellCaseAlt {
39    pub pattern: HaskellPattern,
40    /// Guards; if empty the alternative has a direct body.
41    pub guards: Vec<HaskellGuard>,
42    pub body: Option<HaskellExpr>,
43}
44/// Haskell literal values.
45#[derive(Debug, Clone, PartialEq)]
46pub enum HaskellLit {
47    /// Integer literal: `42`, `-7`
48    Int(i64),
49    /// Floating-point literal: `3.14`
50    Float(f64),
51    /// Character literal: `'a'`
52    Char(char),
53    /// String literal: `"hello"`
54    Str(String),
55    /// Boolean literals `True` / `False`
56    Bool(bool),
57    /// Unit literal `()`
58    Unit,
59}
60/// A statement inside a `do` block.
61#[derive(Debug, Clone, PartialEq)]
62pub enum HaskellDoStmt {
63    /// `x <- action`
64    Bind(String, HaskellExpr),
65    /// `action` (discard result)
66    Stmt(HaskellExpr),
67    /// `let x = expr` inside do
68    LetBind(String, HaskellExpr),
69}
70/// A Haskell `newtype` declaration.
71///
72/// Example: `newtype Name = Name { unName :: String } deriving (Show, Eq)`
73#[derive(Debug, Clone, PartialEq)]
74pub struct HaskellNewtype {
75    /// Type name
76    pub name: String,
77    /// Single type parameter (or empty string)
78    pub type_param: Option<String>,
79    /// Constructor name
80    pub constructor: String,
81    /// Wrapped field name and type
82    pub field: (String, HaskellType),
83    /// Deriving clauses
84    pub deriving_clauses: Vec<String>,
85}
86/// A complete Haskell source module.
87#[derive(Debug, Clone, PartialEq)]
88pub struct HaskellModule {
89    /// Module name: `Main`, `Data.MyLib`
90    pub name: String,
91    /// Explicit export list (empty = export everything)
92    pub exports: Vec<String>,
93    /// Import declarations
94    pub imports: Vec<HaskellImport>,
95    /// Top-level declarations
96    pub declarations: Vec<HaskellDecl>,
97}
98impl HaskellModule {
99    /// Create a new empty module.
100    pub fn new(name: impl Into<String>) -> Self {
101        HaskellModule {
102            name: name.into(),
103            exports: Vec::new(),
104            imports: Vec::new(),
105            declarations: Vec::new(),
106        }
107    }
108    /// Add an import.
109    pub fn add_import(&mut self, imp: HaskellImport) {
110        self.imports.push(imp);
111    }
112    /// Add a top-level declaration.
113    pub fn add_decl(&mut self, decl: HaskellDecl) {
114        self.declarations.push(decl);
115    }
116    /// Emit the complete Haskell source for this module.
117    pub fn emit(&self) -> String {
118        let mut out = String::new();
119        if !self.exports.is_empty() {
120            out.push_str(&format!("module {} (\n", self.name));
121            for (i, exp) in self.exports.iter().enumerate() {
122                if i > 0 {
123                    out.push_str(",\n");
124                }
125                out.push_str(&format!("  {}", exp));
126            }
127            out.push_str("\n) where\n\n");
128        } else {
129            out.push_str(&format!("module {} where\n\n", self.name));
130        }
131        for imp in &self.imports {
132            out.push_str(&format!("{}\n", imp));
133        }
134        if !self.imports.is_empty() {
135            out.push('\n');
136        }
137        for decl in &self.declarations {
138            out.push_str(&format!("{}\n", decl));
139        }
140        out
141    }
142}
143/// Emission statistics for HsExt.
144#[derive(Debug, Clone, Default)]
145pub struct HsExtEmitStats {
146    pub bytes_emitted: usize,
147    pub items_emitted: usize,
148    pub errors: usize,
149    pub warnings: usize,
150    pub elapsed_ms: u64,
151}
152impl HsExtEmitStats {
153    pub fn new() -> Self {
154        HsExtEmitStats::default()
155    }
156    pub fn throughput_bps(&self) -> f64 {
157        if self.elapsed_ms == 0 {
158            0.0
159        } else {
160            self.bytes_emitted as f64 / (self.elapsed_ms as f64 / 1000.0)
161        }
162    }
163    pub fn is_clean(&self) -> bool {
164        self.errors == 0
165    }
166}
167#[allow(dead_code)]
168#[derive(Debug, Clone)]
169pub struct HskLivenessInfo {
170    pub live_in: Vec<std::collections::HashSet<u32>>,
171    pub live_out: Vec<std::collections::HashSet<u32>>,
172    pub defs: Vec<std::collections::HashSet<u32>>,
173    pub uses: Vec<std::collections::HashSet<u32>>,
174}
175impl HskLivenessInfo {
176    #[allow(dead_code)]
177    pub fn new(block_count: usize) -> Self {
178        HskLivenessInfo {
179            live_in: vec![std::collections::HashSet::new(); block_count],
180            live_out: vec![std::collections::HashSet::new(); block_count],
181            defs: vec![std::collections::HashSet::new(); block_count],
182            uses: vec![std::collections::HashSet::new(); block_count],
183        }
184    }
185    #[allow(dead_code)]
186    pub fn add_def(&mut self, block: usize, var: u32) {
187        if block < self.defs.len() {
188            self.defs[block].insert(var);
189        }
190    }
191    #[allow(dead_code)]
192    pub fn add_use(&mut self, block: usize, var: u32) {
193        if block < self.uses.len() {
194            self.uses[block].insert(var);
195        }
196    }
197    #[allow(dead_code)]
198    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
199        self.live_in
200            .get(block)
201            .map(|s| s.contains(&var))
202            .unwrap_or(false)
203    }
204    #[allow(dead_code)]
205    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
206        self.live_out
207            .get(block)
208            .map(|s| s.contains(&var))
209            .unwrap_or(false)
210    }
211}
212#[allow(dead_code)]
213#[derive(Debug, Clone)]
214pub struct HskDominatorTree {
215    pub idom: Vec<Option<u32>>,
216    pub dom_children: Vec<Vec<u32>>,
217    pub dom_depth: Vec<u32>,
218}
219impl HskDominatorTree {
220    #[allow(dead_code)]
221    pub fn new(size: usize) -> Self {
222        HskDominatorTree {
223            idom: vec![None; size],
224            dom_children: vec![Vec::new(); size],
225            dom_depth: vec![0; size],
226        }
227    }
228    #[allow(dead_code)]
229    pub fn set_idom(&mut self, node: usize, idom: u32) {
230        self.idom[node] = Some(idom);
231    }
232    #[allow(dead_code)]
233    pub fn dominates(&self, a: usize, b: usize) -> bool {
234        if a == b {
235            return true;
236        }
237        let mut cur = b;
238        loop {
239            match self.idom[cur] {
240                Some(parent) if parent as usize == a => return true,
241                Some(parent) if parent as usize == cur => return false,
242                Some(parent) => cur = parent as usize,
243                None => return false,
244            }
245        }
246    }
247    #[allow(dead_code)]
248    pub fn depth(&self, node: usize) -> u32 {
249        self.dom_depth.get(node).copied().unwrap_or(0)
250    }
251}
252#[allow(dead_code)]
253#[derive(Debug, Clone)]
254pub struct HskWorklist {
255    pub(super) items: std::collections::VecDeque<u32>,
256    pub(super) in_worklist: std::collections::HashSet<u32>,
257}
258impl HskWorklist {
259    #[allow(dead_code)]
260    pub fn new() -> Self {
261        HskWorklist {
262            items: std::collections::VecDeque::new(),
263            in_worklist: std::collections::HashSet::new(),
264        }
265    }
266    #[allow(dead_code)]
267    pub fn push(&mut self, item: u32) -> bool {
268        if self.in_worklist.insert(item) {
269            self.items.push_back(item);
270            true
271        } else {
272            false
273        }
274    }
275    #[allow(dead_code)]
276    pub fn pop(&mut self) -> Option<u32> {
277        let item = self.items.pop_front()?;
278        self.in_worklist.remove(&item);
279        Some(item)
280    }
281    #[allow(dead_code)]
282    pub fn is_empty(&self) -> bool {
283        self.items.is_empty()
284    }
285    #[allow(dead_code)]
286    pub fn len(&self) -> usize {
287        self.items.len()
288    }
289    #[allow(dead_code)]
290    pub fn contains(&self, item: u32) -> bool {
291        self.in_worklist.contains(&item)
292    }
293}
294/// A text buffer for building HsExt output source code.
295#[derive(Debug, Default)]
296pub struct HsExtSourceBuffer {
297    pub(super) buf: String,
298    pub(super) indent_level: usize,
299    pub(super) indent_str: String,
300}
301impl HsExtSourceBuffer {
302    pub fn new() -> Self {
303        HsExtSourceBuffer {
304            buf: String::new(),
305            indent_level: 0,
306            indent_str: "    ".to_string(),
307        }
308    }
309    pub fn with_indent(mut self, indent: impl Into<String>) -> Self {
310        self.indent_str = indent.into();
311        self
312    }
313    pub fn push_line(&mut self, line: &str) {
314        for _ in 0..self.indent_level {
315            self.buf.push_str(&self.indent_str);
316        }
317        self.buf.push_str(line);
318        self.buf.push('\n');
319    }
320    pub fn push_raw(&mut self, s: &str) {
321        self.buf.push_str(s);
322    }
323    pub fn indent(&mut self) {
324        self.indent_level += 1;
325    }
326    pub fn dedent(&mut self) {
327        self.indent_level = self.indent_level.saturating_sub(1);
328    }
329    pub fn as_str(&self) -> &str {
330        &self.buf
331    }
332    pub fn len(&self) -> usize {
333        self.buf.len()
334    }
335    pub fn is_empty(&self) -> bool {
336        self.buf.is_empty()
337    }
338    pub fn line_count(&self) -> usize {
339        self.buf.lines().count()
340    }
341    pub fn into_string(self) -> String {
342        self.buf
343    }
344    pub fn reset(&mut self) {
345        self.buf.clear();
346        self.indent_level = 0;
347    }
348}
349#[allow(dead_code)]
350#[derive(Debug, Clone)]
351pub struct HskPassConfig {
352    pub phase: HskPassPhase,
353    pub enabled: bool,
354    pub max_iterations: u32,
355    pub debug_output: bool,
356    pub pass_name: String,
357}
358impl HskPassConfig {
359    #[allow(dead_code)]
360    pub fn new(name: impl Into<String>, phase: HskPassPhase) -> Self {
361        HskPassConfig {
362            phase,
363            enabled: true,
364            max_iterations: 10,
365            debug_output: false,
366            pass_name: name.into(),
367        }
368    }
369    #[allow(dead_code)]
370    pub fn disabled(mut self) -> Self {
371        self.enabled = false;
372        self
373    }
374    #[allow(dead_code)]
375    pub fn with_debug(mut self) -> Self {
376        self.debug_output = true;
377        self
378    }
379    #[allow(dead_code)]
380    pub fn max_iter(mut self, n: u32) -> Self {
381        self.max_iterations = n;
382        self
383    }
384}
385/// Heuristic freshness key for HsExt incremental compilation.
386#[derive(Debug, Clone, PartialEq, Eq, Hash)]
387pub struct HsExtIncrKey {
388    pub content_hash: u64,
389    pub config_hash: u64,
390}
391impl HsExtIncrKey {
392    pub fn new(content: u64, config: u64) -> Self {
393        HsExtIncrKey {
394            content_hash: content,
395            config_hash: config,
396        }
397    }
398    pub fn combined_hash(&self) -> u64 {
399        self.content_hash.wrapping_mul(0x9e3779b97f4a7c15) ^ self.config_hash
400    }
401    pub fn matches(&self, other: &HsExtIncrKey) -> bool {
402        self.content_hash == other.content_hash && self.config_hash == other.config_hash
403    }
404}
405/// A single guard in a function equation or case alternative.
406#[derive(Debug, Clone, PartialEq)]
407pub struct HaskellGuard {
408    pub condition: HaskellExpr,
409    pub body: HaskellExpr,
410}
411#[allow(dead_code)]
412#[derive(Debug, Clone, Default)]
413pub struct HskPassStats {
414    pub total_runs: u32,
415    pub successful_runs: u32,
416    pub total_changes: u64,
417    pub time_ms: u64,
418    pub iterations_used: u32,
419}
420impl HskPassStats {
421    #[allow(dead_code)]
422    pub fn new() -> Self {
423        Self::default()
424    }
425    #[allow(dead_code)]
426    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
427        self.total_runs += 1;
428        self.successful_runs += 1;
429        self.total_changes += changes;
430        self.time_ms += time_ms;
431        self.iterations_used = iterations;
432    }
433    #[allow(dead_code)]
434    pub fn average_changes_per_run(&self) -> f64 {
435        if self.total_runs == 0 {
436            return 0.0;
437        }
438        self.total_changes as f64 / self.total_runs as f64
439    }
440    #[allow(dead_code)]
441    pub fn success_rate(&self) -> f64 {
442        if self.total_runs == 0 {
443            return 0.0;
444        }
445        self.successful_runs as f64 / self.total_runs as f64
446    }
447    #[allow(dead_code)]
448    pub fn format_summary(&self) -> String {
449        format!(
450            "Runs: {}/{}, Changes: {}, Time: {}ms",
451            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
452        )
453    }
454}
455/// The Haskell code generation backend.
456pub struct HaskellBackend {
457    pub(super) module: HaskellModule,
458}
459impl HaskellBackend {
460    /// Create a new backend targeting the named module.
461    pub fn new(module_name: impl Into<String>) -> Self {
462        let mut module = HaskellModule::new(module_name);
463        module.add_import(HaskellImport {
464            module: "Prelude".to_string(),
465            qualified: false,
466            alias: None,
467            items: Vec::new(),
468            hiding: Vec::new(),
469        });
470        HaskellBackend { module }
471    }
472    /// Compile a single LCNF function declaration into Haskell and add it to the module.
473    pub fn compile_decl(&mut self, decl: &LcnfFunDecl) {
474        let hs_fn = self.compile_fun(decl);
475        self.module.add_decl(HaskellDecl::Function(hs_fn));
476    }
477    /// Compile an LCNF function to a Haskell function.
478    pub(super) fn compile_fun(&self, decl: &LcnfFunDecl) -> HaskellFunction {
479        let params: Vec<HaskellPattern> = decl
480            .params
481            .iter()
482            .map(|p| HaskellPattern::Var(p.name.clone()))
483            .collect();
484        let body = self.compile_expr(&decl.body);
485        HaskellFunction {
486            name: sanitize_hs_ident(&decl.name),
487            type_annotation: None,
488            equations: vec![HaskellEquation {
489                patterns: params,
490                guards: Vec::new(),
491                body: Some(body),
492                where_clause: Vec::new(),
493            }],
494        }
495    }
496    /// Compile an LCNF expression to a Haskell expression.
497    pub(super) fn compile_expr(&self, expr: &LcnfExpr) -> HaskellExpr {
498        match expr {
499            LcnfExpr::Return(arg) => self.compile_arg(arg),
500            LcnfExpr::Let {
501                name, value, body, ..
502            } => {
503                let rhs_expr = self.compile_let_value(value);
504                let cont_expr = self.compile_expr(body);
505                HaskellExpr::Let(name.clone(), Box::new(rhs_expr), Box::new(cont_expr))
506            }
507            LcnfExpr::Case {
508                scrutinee,
509                alts,
510                default,
511                ..
512            } => {
513                let scrut = HaskellExpr::Var(format!("{}", scrutinee));
514                let mut hs_alts: Vec<HaskellCaseAlt> =
515                    alts.iter().map(|alt| self.compile_alt(alt)).collect();
516                if let Some(def) = default {
517                    let def_expr = self.compile_expr(def);
518                    hs_alts.push(HaskellCaseAlt {
519                        pattern: HaskellPattern::Wildcard,
520                        guards: Vec::new(),
521                        body: Some(def_expr),
522                    });
523                }
524                HaskellExpr::Case(Box::new(scrut), hs_alts)
525            }
526            LcnfExpr::TailCall(func, args) => {
527                let func_expr = self.compile_arg(func);
528                if args.is_empty() {
529                    func_expr
530                } else {
531                    let arg_exprs: Vec<HaskellExpr> =
532                        args.iter().map(|a| self.compile_arg(a)).collect();
533                    HaskellExpr::App(Box::new(func_expr), arg_exprs)
534                }
535            }
536            LcnfExpr::Unreachable => HaskellExpr::Var("undefined".to_string()),
537        }
538    }
539    /// Compile an LCNF let-value to a Haskell expression.
540    pub(super) fn compile_let_value(&self, val: &LcnfLetValue) -> HaskellExpr {
541        match val {
542            LcnfLetValue::App(func, args) => {
543                let func_expr = self.compile_arg(func);
544                if args.is_empty() {
545                    func_expr
546                } else {
547                    let arg_exprs: Vec<HaskellExpr> =
548                        args.iter().map(|a| self.compile_arg(a)).collect();
549                    HaskellExpr::App(Box::new(func_expr), arg_exprs)
550                }
551            }
552            LcnfLetValue::Ctor(name, _tag, args) => {
553                let ctor_expr = HaskellExpr::Var(name.clone());
554                if args.is_empty() {
555                    ctor_expr
556                } else {
557                    let arg_exprs: Vec<HaskellExpr> =
558                        args.iter().map(|a| self.compile_arg(a)).collect();
559                    HaskellExpr::App(Box::new(ctor_expr), arg_exprs)
560                }
561            }
562            LcnfLetValue::Proj(_name, idx, var) => {
563                let accessor = match idx {
564                    0 => "fst",
565                    1 => "snd",
566                    n => return HaskellExpr::Var(format!("_proj{}_{}", n, var)),
567                };
568                HaskellExpr::App(
569                    Box::new(HaskellExpr::Var(accessor.to_string())),
570                    vec![HaskellExpr::Var(format!("{}", var))],
571                )
572            }
573            LcnfLetValue::Lit(lit) => match lit {
574                LcnfLit::Nat(n) => HaskellExpr::Lit(HaskellLit::Int(*n as i64)),
575                LcnfLit::Str(s) => HaskellExpr::Lit(HaskellLit::Str(s.clone())),
576            },
577            LcnfLetValue::Erased | LcnfLetValue::Reset(_) => HaskellExpr::Lit(HaskellLit::Unit),
578            LcnfLetValue::FVar(v) => HaskellExpr::Var(format!("{}", v)),
579            LcnfLetValue::Reuse(_, name, _tag, args) => {
580                let ctor_expr = HaskellExpr::Var(name.clone());
581                if args.is_empty() {
582                    ctor_expr
583                } else {
584                    let arg_exprs: Vec<HaskellExpr> =
585                        args.iter().map(|a| self.compile_arg(a)).collect();
586                    HaskellExpr::App(Box::new(ctor_expr), arg_exprs)
587                }
588            }
589        }
590    }
591    /// Compile an LCNF case alternative.
592    pub(super) fn compile_alt(&self, alt: &LcnfAlt) -> HaskellCaseAlt {
593        let body = self.compile_expr(&alt.body);
594        let pat = HaskellPattern::Constructor(
595            alt.ctor_name.clone(),
596            alt.params
597                .iter()
598                .map(|p| HaskellPattern::Var(p.name.clone()))
599                .collect(),
600        );
601        HaskellCaseAlt {
602            pattern: pat,
603            guards: Vec::new(),
604            body: Some(body),
605        }
606    }
607    /// Compile an LCNF argument to a Haskell expression.
608    pub(super) fn compile_arg(&self, arg: &LcnfArg) -> HaskellExpr {
609        match arg {
610            LcnfArg::Var(v) => HaskellExpr::Var(format!("{}", v)),
611            LcnfArg::Lit(lit) => match lit {
612                LcnfLit::Nat(n) => HaskellExpr::Lit(HaskellLit::Int(*n as i64)),
613                LcnfLit::Str(s) => HaskellExpr::Lit(HaskellLit::Str(s.clone())),
614            },
615            LcnfArg::Erased | LcnfArg::Type(_) => HaskellExpr::Lit(HaskellLit::Unit),
616        }
617    }
618    /// Emit the complete Haskell module source.
619    pub fn emit_module(&self) -> String {
620        self.module.emit()
621    }
622}
623/// A Haskell `instance` declaration.
624///
625/// Example:
626/// ```text
627/// instance Show Expr where
628///   show (Lit n) = show n
629///   show (Add l r) = show l ++ " + " ++ show r
630/// ```
631#[derive(Debug, Clone, PartialEq)]
632pub struct HaskellInstance {
633    /// Class being instantiated: `Show`
634    pub class: String,
635    /// Type being instantiated: `Maybe Int`
636    pub instance_type: HaskellType,
637    /// Context constraints: `Show a`
638    pub context: Vec<HaskellType>,
639    /// Method implementations as functions
640    pub where_clause: Vec<HaskellFunction>,
641}
642/// A version tag for HsExt output artifacts.
643#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
644pub struct HsExtVersion {
645    pub major: u32,
646    pub minor: u32,
647    pub patch: u32,
648    pub pre: Option<String>,
649}
650impl HsExtVersion {
651    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
652        HsExtVersion {
653            major,
654            minor,
655            patch,
656            pre: None,
657        }
658    }
659    pub fn with_pre(mut self, pre: impl Into<String>) -> Self {
660        self.pre = Some(pre.into());
661        self
662    }
663    pub fn is_stable(&self) -> bool {
664        self.pre.is_none()
665    }
666    pub fn is_compatible_with(&self, other: &HsExtVersion) -> bool {
667        self.major == other.major && self.minor >= other.minor
668    }
669}
670#[allow(dead_code)]
671pub struct HskConstantFoldingHelper;
672impl HskConstantFoldingHelper {
673    #[allow(dead_code)]
674    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
675        a.checked_add(b)
676    }
677    #[allow(dead_code)]
678    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
679        a.checked_sub(b)
680    }
681    #[allow(dead_code)]
682    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
683        a.checked_mul(b)
684    }
685    #[allow(dead_code)]
686    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
687        if b == 0 {
688            None
689        } else {
690            a.checked_div(b)
691        }
692    }
693    #[allow(dead_code)]
694    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
695        a + b
696    }
697    #[allow(dead_code)]
698    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
699        a * b
700    }
701    #[allow(dead_code)]
702    pub fn fold_neg_i64(a: i64) -> Option<i64> {
703        a.checked_neg()
704    }
705    #[allow(dead_code)]
706    pub fn fold_not_bool(a: bool) -> bool {
707        !a
708    }
709    #[allow(dead_code)]
710    pub fn fold_and_bool(a: bool, b: bool) -> bool {
711        a && b
712    }
713    #[allow(dead_code)]
714    pub fn fold_or_bool(a: bool, b: bool) -> bool {
715        a || b
716    }
717    #[allow(dead_code)]
718    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
719        a.checked_shl(b)
720    }
721    #[allow(dead_code)]
722    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
723        a.checked_shr(b)
724    }
725    #[allow(dead_code)]
726    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
727        if b == 0 {
728            None
729        } else {
730            Some(a % b)
731        }
732    }
733    #[allow(dead_code)]
734    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
735        a & b
736    }
737    #[allow(dead_code)]
738    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
739        a | b
740    }
741    #[allow(dead_code)]
742    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
743        a ^ b
744    }
745    #[allow(dead_code)]
746    pub fn fold_bitnot_i64(a: i64) -> i64 {
747        !a
748    }
749}
750/// A feature flag set for HsExt capabilities.
751#[derive(Debug, Clone, Default)]
752pub struct HsExtFeatures {
753    pub(super) flags: std::collections::HashSet<String>,
754}
755impl HsExtFeatures {
756    pub fn new() -> Self {
757        HsExtFeatures::default()
758    }
759    pub fn enable(&mut self, flag: impl Into<String>) {
760        self.flags.insert(flag.into());
761    }
762    pub fn disable(&mut self, flag: &str) {
763        self.flags.remove(flag);
764    }
765    pub fn is_enabled(&self, flag: &str) -> bool {
766        self.flags.contains(flag)
767    }
768    pub fn len(&self) -> usize {
769        self.flags.len()
770    }
771    pub fn is_empty(&self) -> bool {
772        self.flags.is_empty()
773    }
774    pub fn union(&self, other: &HsExtFeatures) -> HsExtFeatures {
775        HsExtFeatures {
776            flags: self.flags.union(&other.flags).cloned().collect(),
777        }
778    }
779    pub fn intersection(&self, other: &HsExtFeatures) -> HsExtFeatures {
780        HsExtFeatures {
781            flags: self.flags.intersection(&other.flags).cloned().collect(),
782        }
783    }
784}
785/// A qualifier in a list comprehension `[ e | q1, q2, ... ]`.
786#[derive(Debug, Clone, PartialEq)]
787pub enum HsListQual {
788    /// Generator: `x <- xs`
789    Generator(String, HaskellExpr),
790    /// Guard: `x > 0`
791    Guard(HaskellExpr),
792    /// Let binding: `let y = f x`
793    LetBind(String, HaskellExpr),
794}
795/// A fixed-capacity ring buffer of strings (for recent-event logging in HsExt).
796#[derive(Debug)]
797pub struct HsExtEventLog {
798    pub(super) entries: std::collections::VecDeque<String>,
799    pub(super) capacity: usize,
800}
801impl HsExtEventLog {
802    pub fn new(capacity: usize) -> Self {
803        HsExtEventLog {
804            entries: std::collections::VecDeque::with_capacity(capacity),
805            capacity,
806        }
807    }
808    pub fn push(&mut self, event: impl Into<String>) {
809        if self.entries.len() >= self.capacity {
810            self.entries.pop_front();
811        }
812        self.entries.push_back(event.into());
813    }
814    pub fn iter(&self) -> impl Iterator<Item = &String> {
815        self.entries.iter()
816    }
817    pub fn len(&self) -> usize {
818        self.entries.len()
819    }
820    pub fn is_empty(&self) -> bool {
821        self.entries.is_empty()
822    }
823    pub fn capacity(&self) -> usize {
824        self.capacity
825    }
826    pub fn clear(&mut self) {
827        self.entries.clear();
828    }
829}
830#[allow(dead_code)]
831pub struct HskPassRegistry {
832    pub(super) configs: Vec<HskPassConfig>,
833    pub(super) stats: std::collections::HashMap<String, HskPassStats>,
834}
835impl HskPassRegistry {
836    #[allow(dead_code)]
837    pub fn new() -> Self {
838        HskPassRegistry {
839            configs: Vec::new(),
840            stats: std::collections::HashMap::new(),
841        }
842    }
843    #[allow(dead_code)]
844    pub fn register(&mut self, config: HskPassConfig) {
845        self.stats
846            .insert(config.pass_name.clone(), HskPassStats::new());
847        self.configs.push(config);
848    }
849    #[allow(dead_code)]
850    pub fn enabled_passes(&self) -> Vec<&HskPassConfig> {
851        self.configs.iter().filter(|c| c.enabled).collect()
852    }
853    #[allow(dead_code)]
854    pub fn get_stats(&self, name: &str) -> Option<&HskPassStats> {
855        self.stats.get(name)
856    }
857    #[allow(dead_code)]
858    pub fn total_passes(&self) -> usize {
859        self.configs.len()
860    }
861    #[allow(dead_code)]
862    pub fn enabled_count(&self) -> usize {
863        self.enabled_passes().len()
864    }
865    #[allow(dead_code)]
866    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
867        if let Some(stats) = self.stats.get_mut(name) {
868            stats.record_run(changes, time_ms, iter);
869        }
870    }
871}
872#[allow(dead_code)]
873#[derive(Debug, Clone, PartialEq)]
874pub enum HskPassPhase {
875    Analysis,
876    Transformation,
877    Verification,
878    Cleanup,
879}
880impl HskPassPhase {
881    #[allow(dead_code)]
882    pub fn name(&self) -> &str {
883        match self {
884            HskPassPhase::Analysis => "analysis",
885            HskPassPhase::Transformation => "transformation",
886            HskPassPhase::Verification => "verification",
887            HskPassPhase::Cleanup => "cleanup",
888        }
889    }
890    #[allow(dead_code)]
891    pub fn is_modifying(&self) -> bool {
892        matches!(self, HskPassPhase::Transformation | HskPassPhase::Cleanup)
893    }
894}
895/// A Haskell function definition (possibly with multiple equations).
896///
897/// Example:
898/// ```text
899/// factorial :: Int -> Int
900/// factorial 0 = 1
901/// factorial n = n * factorial (n - 1)
902/// ```
903#[derive(Debug, Clone, PartialEq)]
904pub struct HaskellFunction {
905    /// Function name
906    pub name: String,
907    /// Type signature annotation
908    pub type_annotation: Option<HaskellType>,
909    /// Equations (pattern-matched clauses)
910    pub equations: Vec<HaskellEquation>,
911}
912/// Tracks declared names for HsExt scope analysis.
913#[derive(Debug, Default)]
914pub struct HsExtNameScope {
915    pub(super) declared: std::collections::HashSet<String>,
916    pub(super) depth: usize,
917    pub(super) parent: Option<Box<HsExtNameScope>>,
918}
919impl HsExtNameScope {
920    pub fn new() -> Self {
921        HsExtNameScope::default()
922    }
923    pub fn declare(&mut self, name: impl Into<String>) -> bool {
924        self.declared.insert(name.into())
925    }
926    pub fn is_declared(&self, name: &str) -> bool {
927        self.declared.contains(name)
928    }
929    pub fn push_scope(self) -> Self {
930        HsExtNameScope {
931            declared: std::collections::HashSet::new(),
932            depth: self.depth + 1,
933            parent: Some(Box::new(self)),
934        }
935    }
936    pub fn pop_scope(self) -> Self {
937        *self.parent.unwrap_or_default()
938    }
939    pub fn depth(&self) -> usize {
940        self.depth
941    }
942    pub fn len(&self) -> usize {
943        self.declared.len()
944    }
945}
946#[allow(dead_code)]
947#[derive(Debug, Clone)]
948pub struct HskCacheEntry {
949    pub key: String,
950    pub data: Vec<u8>,
951    pub timestamp: u64,
952    pub valid: bool,
953}
954/// Haskell pattern AST used in `case` and function equations.
955#[derive(Debug, Clone, PartialEq)]
956pub enum HaskellPattern {
957    /// `_` — wildcard
958    Wildcard,
959    /// Variable binding: `x`
960    Var(String),
961    /// Literal pattern: `42`, `'a'`, `True`
962    Lit(HaskellLit),
963    /// Tuple pattern: `(a, b, c)`
964    Tuple(Vec<HaskellPattern>),
965    /// List pattern: `[a, b, c]`
966    List(Vec<HaskellPattern>),
967    /// Cons pattern: `x : xs`
968    Cons(Box<HaskellPattern>, Box<HaskellPattern>),
969    /// Constructor pattern: `Just x`, `Left a`
970    Constructor(String, Vec<HaskellPattern>),
971    /// As pattern: `xs@(x:rest)`
972    As(String, Box<HaskellPattern>),
973    /// Lazy (irrefutable) pattern: `~pat`
974    LazyPat(Box<HaskellPattern>),
975}
976/// Haskell expression AST.
977#[derive(Debug, Clone, PartialEq)]
978pub enum HaskellExpr {
979    /// Literal value
980    Lit(HaskellLit),
981    /// Variable or constructor reference: `foo`, `Just`, `(:)`
982    Var(String),
983    /// Function application: `f x y`
984    App(Box<HaskellExpr>, Vec<HaskellExpr>),
985    /// Lambda: `\x y -> body`
986    Lambda(Vec<HaskellPattern>, Box<HaskellExpr>),
987    /// Let expression: `let x = e1 in e2`
988    Let(String, Box<HaskellExpr>, Box<HaskellExpr>),
989    /// Where clause as an expression wrapper
990    Where(Box<HaskellExpr>, Vec<HaskellFunction>),
991    /// If-then-else: `if c then t else e`
992    If(Box<HaskellExpr>, Box<HaskellExpr>, Box<HaskellExpr>),
993    /// Case expression
994    Case(Box<HaskellExpr>, Vec<HaskellCaseAlt>),
995    /// Do notation block
996    Do(Vec<HaskellDoStmt>),
997    /// List comprehension: `[ body | quals ]`
998    ListComp(Box<HaskellExpr>, Vec<HsListQual>),
999    /// Tuple: `(a, b, c)`
1000    Tuple(Vec<HaskellExpr>),
1001    /// List: `[a, b, c]`
1002    List(Vec<HaskellExpr>),
1003    /// Arithmetic negation: `negate x`
1004    Neg(Box<HaskellExpr>),
1005    /// Infix application: `x `op` y` or `x + y`
1006    InfixApp(Box<HaskellExpr>, String, Box<HaskellExpr>),
1007    /// Operator section / raw operator reference: `(+)`, `map`
1008    Operator(String),
1009    /// Type annotation: `expr :: Type`
1010    TypeAnnotation(Box<HaskellExpr>, HaskellType),
1011}
1012/// A Haskell `class` declaration.
1013///
1014/// Example:
1015/// ```text
1016/// class Functor f where
1017///   fmap :: (a -> b) -> f a -> f b
1018/// ```
1019#[derive(Debug, Clone, PartialEq)]
1020pub struct HaskellTypeClass {
1021    /// Class name: `Functor`
1022    pub name: String,
1023    /// Class type parameters: `f`
1024    pub type_params: Vec<String>,
1025    /// Superclass constraints: `Eq a`, `Ord a`
1026    pub superclasses: Vec<HaskellType>,
1027    /// Methods: (name, signature, optional default body)
1028    pub methods: Vec<(String, HaskellType, Option<HaskellExpr>)>,
1029}
1030/// Haskell type representation for type-directed code generation.
1031#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1032pub enum HaskellType {
1033    /// `Int` — fixed-precision signed integer (at least 30-bit)
1034    Int,
1035    /// `Integer` — arbitrary-precision integer
1036    Integer,
1037    /// `Double` — 64-bit IEEE floating-point
1038    Double,
1039    /// `Float` — 32-bit IEEE floating-point
1040    Float,
1041    /// `Bool` — boolean
1042    Bool,
1043    /// `Char` — Unicode character
1044    Char,
1045    /// `String` — alias for `[Char]`
1046    HsString,
1047    /// `()` — unit type
1048    Unit,
1049    /// `IO a` — IO monad
1050    IO(Box<HaskellType>),
1051    /// `[a]` — list of a
1052    List(Box<HaskellType>),
1053    /// `Maybe a` — optional value
1054    Maybe(Box<HaskellType>),
1055    /// `Either a b` — sum type
1056    Either(Box<HaskellType>, Box<HaskellType>),
1057    /// `(a, b, ...)` — tuple
1058    Tuple(Vec<HaskellType>),
1059    /// `a -> b` — function type
1060    Fun(Box<HaskellType>, Box<HaskellType>),
1061    /// Named type (data, newtype, type synonym)
1062    Custom(String),
1063    /// Polymorphic type variable: `a`, `b`, `f`
1064    Polymorphic(String),
1065    /// Type class constraint: `Eq a`, `Functor f`
1066    Constraint(String, Vec<HaskellType>),
1067}
1068/// A single equation of a Haskell function definition.
1069///
1070/// Example: `factorial 0 = 1`
1071#[derive(Debug, Clone, PartialEq)]
1072pub struct HaskellEquation {
1073    /// Argument patterns for this equation
1074    pub patterns: Vec<HaskellPattern>,
1075    /// Optional guards; if empty, body is unconditional
1076    pub guards: Vec<HaskellGuard>,
1077    /// Right-hand side (if no guards)
1078    pub body: Option<HaskellExpr>,
1079    /// Where clause local bindings
1080    pub where_clause: Vec<HaskellFunction>,
1081}
1082/// A Haskell import declaration.
1083#[derive(Debug, Clone, PartialEq)]
1084pub struct HaskellImport {
1085    /// Module name: `Data.List`
1086    pub module: String,
1087    /// `qualified` import
1088    pub qualified: bool,
1089    /// `as` alias
1090    pub alias: Option<String>,
1091    /// Explicit import list (empty = import everything)
1092    pub items: Vec<String>,
1093    /// `hiding` list
1094    pub hiding: Vec<String>,
1095}
1096/// A diagnostic message from a HsExt pass.
1097#[derive(Debug, Clone)]
1098pub struct HsExtDiagMsg {
1099    pub severity: HsExtDiagSeverity,
1100    pub pass: String,
1101    pub message: String,
1102}
1103impl HsExtDiagMsg {
1104    pub fn error(pass: impl Into<String>, msg: impl Into<String>) -> Self {
1105        HsExtDiagMsg {
1106            severity: HsExtDiagSeverity::Error,
1107            pass: pass.into(),
1108            message: msg.into(),
1109        }
1110    }
1111    pub fn warning(pass: impl Into<String>, msg: impl Into<String>) -> Self {
1112        HsExtDiagMsg {
1113            severity: HsExtDiagSeverity::Warning,
1114            pass: pass.into(),
1115            message: msg.into(),
1116        }
1117    }
1118    pub fn note(pass: impl Into<String>, msg: impl Into<String>) -> Self {
1119        HsExtDiagMsg {
1120            severity: HsExtDiagSeverity::Note,
1121            pass: pass.into(),
1122            message: msg.into(),
1123        }
1124    }
1125}
1126/// Collects HsExt diagnostics.
1127#[derive(Debug, Default)]
1128pub struct HsExtDiagCollector {
1129    pub(super) msgs: Vec<HsExtDiagMsg>,
1130}
1131impl HsExtDiagCollector {
1132    pub fn new() -> Self {
1133        HsExtDiagCollector::default()
1134    }
1135    pub fn emit(&mut self, d: HsExtDiagMsg) {
1136        self.msgs.push(d);
1137    }
1138    pub fn has_errors(&self) -> bool {
1139        self.msgs
1140            .iter()
1141            .any(|d| d.severity == HsExtDiagSeverity::Error)
1142    }
1143    pub fn errors(&self) -> Vec<&HsExtDiagMsg> {
1144        self.msgs
1145            .iter()
1146            .filter(|d| d.severity == HsExtDiagSeverity::Error)
1147            .collect()
1148    }
1149    pub fn warnings(&self) -> Vec<&HsExtDiagMsg> {
1150        self.msgs
1151            .iter()
1152            .filter(|d| d.severity == HsExtDiagSeverity::Warning)
1153            .collect()
1154    }
1155    pub fn len(&self) -> usize {
1156        self.msgs.len()
1157    }
1158    pub fn is_empty(&self) -> bool {
1159        self.msgs.is_empty()
1160    }
1161    pub fn clear(&mut self) {
1162        self.msgs.clear();
1163    }
1164}
1165#[allow(dead_code)]
1166#[derive(Debug, Clone)]
1167pub struct HskAnalysisCache {
1168    pub(super) entries: std::collections::HashMap<String, HskCacheEntry>,
1169    pub(super) max_size: usize,
1170    pub(super) hits: u64,
1171    pub(super) misses: u64,
1172}
1173impl HskAnalysisCache {
1174    #[allow(dead_code)]
1175    pub fn new(max_size: usize) -> Self {
1176        HskAnalysisCache {
1177            entries: std::collections::HashMap::new(),
1178            max_size,
1179            hits: 0,
1180            misses: 0,
1181        }
1182    }
1183    #[allow(dead_code)]
1184    pub fn get(&mut self, key: &str) -> Option<&HskCacheEntry> {
1185        if self.entries.contains_key(key) {
1186            self.hits += 1;
1187            self.entries.get(key)
1188        } else {
1189            self.misses += 1;
1190            None
1191        }
1192    }
1193    #[allow(dead_code)]
1194    pub fn insert(&mut self, key: String, data: Vec<u8>) {
1195        if self.entries.len() >= self.max_size {
1196            if let Some(oldest) = self.entries.keys().next().cloned() {
1197                self.entries.remove(&oldest);
1198            }
1199        }
1200        self.entries.insert(
1201            key.clone(),
1202            HskCacheEntry {
1203                key,
1204                data,
1205                timestamp: 0,
1206                valid: true,
1207            },
1208        );
1209    }
1210    #[allow(dead_code)]
1211    pub fn invalidate(&mut self, key: &str) {
1212        if let Some(entry) = self.entries.get_mut(key) {
1213            entry.valid = false;
1214        }
1215    }
1216    #[allow(dead_code)]
1217    pub fn clear(&mut self) {
1218        self.entries.clear();
1219    }
1220    #[allow(dead_code)]
1221    pub fn hit_rate(&self) -> f64 {
1222        let total = self.hits + self.misses;
1223        if total == 0 {
1224            return 0.0;
1225        }
1226        self.hits as f64 / total as f64
1227    }
1228    #[allow(dead_code)]
1229    pub fn size(&self) -> usize {
1230        self.entries.len()
1231    }
1232}
1233#[allow(dead_code)]
1234#[derive(Debug, Clone)]
1235pub struct HskDepGraph {
1236    pub(super) nodes: Vec<u32>,
1237    pub(super) edges: Vec<(u32, u32)>,
1238}
1239impl HskDepGraph {
1240    #[allow(dead_code)]
1241    pub fn new() -> Self {
1242        HskDepGraph {
1243            nodes: Vec::new(),
1244            edges: Vec::new(),
1245        }
1246    }
1247    #[allow(dead_code)]
1248    pub fn add_node(&mut self, id: u32) {
1249        if !self.nodes.contains(&id) {
1250            self.nodes.push(id);
1251        }
1252    }
1253    #[allow(dead_code)]
1254    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1255        self.add_node(dep);
1256        self.add_node(dependent);
1257        self.edges.push((dep, dependent));
1258    }
1259    #[allow(dead_code)]
1260    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1261        self.edges
1262            .iter()
1263            .filter(|(d, _)| *d == node)
1264            .map(|(_, dep)| *dep)
1265            .collect()
1266    }
1267    #[allow(dead_code)]
1268    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1269        self.edges
1270            .iter()
1271            .filter(|(_, dep)| *dep == node)
1272            .map(|(d, _)| *d)
1273            .collect()
1274    }
1275    #[allow(dead_code)]
1276    pub fn topological_sort(&self) -> Vec<u32> {
1277        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1278        for &n in &self.nodes {
1279            in_degree.insert(n, 0);
1280        }
1281        for (_, dep) in &self.edges {
1282            *in_degree.entry(*dep).or_insert(0) += 1;
1283        }
1284        let mut queue: std::collections::VecDeque<u32> = self
1285            .nodes
1286            .iter()
1287            .filter(|&&n| in_degree[&n] == 0)
1288            .copied()
1289            .collect();
1290        let mut result = Vec::new();
1291        while let Some(node) = queue.pop_front() {
1292            result.push(node);
1293            for dep in self.dependents_of(node) {
1294                let cnt = in_degree.entry(dep).or_insert(0);
1295                *cnt = cnt.saturating_sub(1);
1296                if *cnt == 0 {
1297                    queue.push_back(dep);
1298                }
1299            }
1300        }
1301        result
1302    }
1303    #[allow(dead_code)]
1304    pub fn has_cycle(&self) -> bool {
1305        self.topological_sort().len() < self.nodes.len()
1306    }
1307}
1308/// Pipeline profiler for HsExt.
1309#[derive(Debug, Default)]
1310pub struct HsExtProfiler {
1311    pub(super) timings: Vec<HsExtPassTiming>,
1312}
1313impl HsExtProfiler {
1314    pub fn new() -> Self {
1315        HsExtProfiler::default()
1316    }
1317    pub fn record(&mut self, t: HsExtPassTiming) {
1318        self.timings.push(t);
1319    }
1320    pub fn total_elapsed_us(&self) -> u64 {
1321        self.timings.iter().map(|t| t.elapsed_us).sum()
1322    }
1323    pub fn slowest_pass(&self) -> Option<&HsExtPassTiming> {
1324        self.timings.iter().max_by_key(|t| t.elapsed_us)
1325    }
1326    pub fn num_passes(&self) -> usize {
1327        self.timings.len()
1328    }
1329    pub fn profitable_passes(&self) -> Vec<&HsExtPassTiming> {
1330        self.timings.iter().filter(|t| t.is_profitable()).collect()
1331    }
1332}
1333/// Pass-timing record for HsExt profiler.
1334#[derive(Debug, Clone)]
1335pub struct HsExtPassTiming {
1336    pub pass_name: String,
1337    pub elapsed_us: u64,
1338    pub items_processed: usize,
1339    pub bytes_before: usize,
1340    pub bytes_after: usize,
1341}
1342impl HsExtPassTiming {
1343    pub fn new(
1344        pass_name: impl Into<String>,
1345        elapsed_us: u64,
1346        items: usize,
1347        before: usize,
1348        after: usize,
1349    ) -> Self {
1350        HsExtPassTiming {
1351            pass_name: pass_name.into(),
1352            elapsed_us,
1353            items_processed: items,
1354            bytes_before: before,
1355            bytes_after: after,
1356        }
1357    }
1358    pub fn throughput_mps(&self) -> f64 {
1359        if self.elapsed_us == 0 {
1360            0.0
1361        } else {
1362            self.items_processed as f64 / (self.elapsed_us as f64 / 1_000_000.0)
1363        }
1364    }
1365    pub fn size_ratio(&self) -> f64 {
1366        if self.bytes_before == 0 {
1367            1.0
1368        } else {
1369            self.bytes_after as f64 / self.bytes_before as f64
1370        }
1371    }
1372    pub fn is_profitable(&self) -> bool {
1373        self.size_ratio() <= 1.05
1374    }
1375}
1376/// A monotonically increasing ID generator for HsExt.
1377#[derive(Debug, Default)]
1378pub struct HsExtIdGen {
1379    pub(super) next: u32,
1380}
1381impl HsExtIdGen {
1382    pub fn new() -> Self {
1383        HsExtIdGen::default()
1384    }
1385    pub fn next_id(&mut self) -> u32 {
1386        let id = self.next;
1387        self.next += 1;
1388        id
1389    }
1390    pub fn peek_next(&self) -> u32 {
1391        self.next
1392    }
1393    pub fn reset(&mut self) {
1394        self.next = 0;
1395    }
1396    pub fn skip(&mut self, n: u32) {
1397        self.next += n;
1398    }
1399}
1400/// Severity of a HsExt diagnostic.
1401#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1402pub enum HsExtDiagSeverity {
1403    Note,
1404    Warning,
1405    Error,
1406}
1407/// A generic key-value configuration store for HsExt.
1408#[derive(Debug, Clone, Default)]
1409pub struct HsExtConfig {
1410    pub(super) entries: std::collections::HashMap<String, String>,
1411}
1412impl HsExtConfig {
1413    pub fn new() -> Self {
1414        HsExtConfig::default()
1415    }
1416    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
1417        self.entries.insert(key.into(), value.into());
1418    }
1419    pub fn get(&self, key: &str) -> Option<&str> {
1420        self.entries.get(key).map(|s| s.as_str())
1421    }
1422    pub fn get_bool(&self, key: &str) -> bool {
1423        matches!(self.get(key), Some("true") | Some("1") | Some("yes"))
1424    }
1425    pub fn get_int(&self, key: &str) -> Option<i64> {
1426        self.get(key)?.parse().ok()
1427    }
1428    pub fn len(&self) -> usize {
1429        self.entries.len()
1430    }
1431    pub fn is_empty(&self) -> bool {
1432        self.entries.is_empty()
1433    }
1434}