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::Int(i) => HaskellExpr::Lit(HaskellLit::Int(*i)),
576                LcnfLit::Str(s) => HaskellExpr::Lit(HaskellLit::Str(s.clone())),
577            },
578            LcnfLetValue::Erased | LcnfLetValue::Reset(_) => HaskellExpr::Lit(HaskellLit::Unit),
579            LcnfLetValue::FVar(v) => HaskellExpr::Var(format!("{}", v)),
580            LcnfLetValue::Reuse(_, name, _tag, args) => {
581                let ctor_expr = HaskellExpr::Var(name.clone());
582                if args.is_empty() {
583                    ctor_expr
584                } else {
585                    let arg_exprs: Vec<HaskellExpr> =
586                        args.iter().map(|a| self.compile_arg(a)).collect();
587                    HaskellExpr::App(Box::new(ctor_expr), arg_exprs)
588                }
589            }
590        }
591    }
592    /// Compile an LCNF case alternative.
593    pub(super) fn compile_alt(&self, alt: &LcnfAlt) -> HaskellCaseAlt {
594        let body = self.compile_expr(&alt.body);
595        let pat = HaskellPattern::Constructor(
596            alt.ctor_name.clone(),
597            alt.params
598                .iter()
599                .map(|p| HaskellPattern::Var(p.name.clone()))
600                .collect(),
601        );
602        HaskellCaseAlt {
603            pattern: pat,
604            guards: Vec::new(),
605            body: Some(body),
606        }
607    }
608    /// Compile an LCNF argument to a Haskell expression.
609    pub(super) fn compile_arg(&self, arg: &LcnfArg) -> HaskellExpr {
610        match arg {
611            LcnfArg::Var(v) => HaskellExpr::Var(format!("{}", v)),
612            LcnfArg::Lit(lit) => match lit {
613                LcnfLit::Nat(n) => HaskellExpr::Lit(HaskellLit::Int(*n as i64)),
614                LcnfLit::Int(i) => HaskellExpr::Lit(HaskellLit::Int(*i)),
615                LcnfLit::Str(s) => HaskellExpr::Lit(HaskellLit::Str(s.clone())),
616            },
617            LcnfArg::Erased | LcnfArg::Type(_) => HaskellExpr::Lit(HaskellLit::Unit),
618        }
619    }
620    /// Emit the complete Haskell module source.
621    pub fn emit_module(&self) -> String {
622        self.module.emit()
623    }
624}
625/// A Haskell `instance` declaration.
626///
627/// Example:
628/// ```text
629/// instance Show Expr where
630///   show (Lit n) = show n
631///   show (Add l r) = show l ++ " + " ++ show r
632/// ```
633#[derive(Debug, Clone, PartialEq)]
634pub struct HaskellInstance {
635    /// Class being instantiated: `Show`
636    pub class: String,
637    /// Type being instantiated: `Maybe Int`
638    pub instance_type: HaskellType,
639    /// Context constraints: `Show a`
640    pub context: Vec<HaskellType>,
641    /// Method implementations as functions
642    pub where_clause: Vec<HaskellFunction>,
643}
644/// A version tag for HsExt output artifacts.
645#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
646pub struct HsExtVersion {
647    pub major: u32,
648    pub minor: u32,
649    pub patch: u32,
650    pub pre: Option<String>,
651}
652impl HsExtVersion {
653    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
654        HsExtVersion {
655            major,
656            minor,
657            patch,
658            pre: None,
659        }
660    }
661    pub fn with_pre(mut self, pre: impl Into<String>) -> Self {
662        self.pre = Some(pre.into());
663        self
664    }
665    pub fn is_stable(&self) -> bool {
666        self.pre.is_none()
667    }
668    pub fn is_compatible_with(&self, other: &HsExtVersion) -> bool {
669        self.major == other.major && self.minor >= other.minor
670    }
671}
672#[allow(dead_code)]
673pub struct HskConstantFoldingHelper;
674impl HskConstantFoldingHelper {
675    #[allow(dead_code)]
676    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
677        a.checked_add(b)
678    }
679    #[allow(dead_code)]
680    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
681        a.checked_sub(b)
682    }
683    #[allow(dead_code)]
684    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
685        a.checked_mul(b)
686    }
687    #[allow(dead_code)]
688    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
689        if b == 0 {
690            None
691        } else {
692            a.checked_div(b)
693        }
694    }
695    #[allow(dead_code)]
696    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
697        a + b
698    }
699    #[allow(dead_code)]
700    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
701        a * b
702    }
703    #[allow(dead_code)]
704    pub fn fold_neg_i64(a: i64) -> Option<i64> {
705        a.checked_neg()
706    }
707    #[allow(dead_code)]
708    pub fn fold_not_bool(a: bool) -> bool {
709        !a
710    }
711    #[allow(dead_code)]
712    pub fn fold_and_bool(a: bool, b: bool) -> bool {
713        a && b
714    }
715    #[allow(dead_code)]
716    pub fn fold_or_bool(a: bool, b: bool) -> bool {
717        a || b
718    }
719    #[allow(dead_code)]
720    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
721        a.checked_shl(b)
722    }
723    #[allow(dead_code)]
724    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
725        a.checked_shr(b)
726    }
727    #[allow(dead_code)]
728    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
729        if b == 0 {
730            None
731        } else {
732            Some(a % b)
733        }
734    }
735    #[allow(dead_code)]
736    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
737        a & b
738    }
739    #[allow(dead_code)]
740    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
741        a | b
742    }
743    #[allow(dead_code)]
744    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
745        a ^ b
746    }
747    #[allow(dead_code)]
748    pub fn fold_bitnot_i64(a: i64) -> i64 {
749        !a
750    }
751}
752/// A feature flag set for HsExt capabilities.
753#[derive(Debug, Clone, Default)]
754pub struct HsExtFeatures {
755    pub(super) flags: std::collections::HashSet<String>,
756}
757impl HsExtFeatures {
758    pub fn new() -> Self {
759        HsExtFeatures::default()
760    }
761    pub fn enable(&mut self, flag: impl Into<String>) {
762        self.flags.insert(flag.into());
763    }
764    pub fn disable(&mut self, flag: &str) {
765        self.flags.remove(flag);
766    }
767    pub fn is_enabled(&self, flag: &str) -> bool {
768        self.flags.contains(flag)
769    }
770    pub fn len(&self) -> usize {
771        self.flags.len()
772    }
773    pub fn is_empty(&self) -> bool {
774        self.flags.is_empty()
775    }
776    pub fn union(&self, other: &HsExtFeatures) -> HsExtFeatures {
777        HsExtFeatures {
778            flags: self.flags.union(&other.flags).cloned().collect(),
779        }
780    }
781    pub fn intersection(&self, other: &HsExtFeatures) -> HsExtFeatures {
782        HsExtFeatures {
783            flags: self.flags.intersection(&other.flags).cloned().collect(),
784        }
785    }
786}
787/// A qualifier in a list comprehension `[ e | q1, q2, ... ]`.
788#[derive(Debug, Clone, PartialEq)]
789pub enum HsListQual {
790    /// Generator: `x <- xs`
791    Generator(String, HaskellExpr),
792    /// Guard: `x > 0`
793    Guard(HaskellExpr),
794    /// Let binding: `let y = f x`
795    LetBind(String, HaskellExpr),
796}
797/// A fixed-capacity ring buffer of strings (for recent-event logging in HsExt).
798#[derive(Debug)]
799pub struct HsExtEventLog {
800    pub(super) entries: std::collections::VecDeque<String>,
801    pub(super) capacity: usize,
802}
803impl HsExtEventLog {
804    pub fn new(capacity: usize) -> Self {
805        HsExtEventLog {
806            entries: std::collections::VecDeque::with_capacity(capacity),
807            capacity,
808        }
809    }
810    pub fn push(&mut self, event: impl Into<String>) {
811        if self.entries.len() >= self.capacity {
812            self.entries.pop_front();
813        }
814        self.entries.push_back(event.into());
815    }
816    pub fn iter(&self) -> impl Iterator<Item = &String> {
817        self.entries.iter()
818    }
819    pub fn len(&self) -> usize {
820        self.entries.len()
821    }
822    pub fn is_empty(&self) -> bool {
823        self.entries.is_empty()
824    }
825    pub fn capacity(&self) -> usize {
826        self.capacity
827    }
828    pub fn clear(&mut self) {
829        self.entries.clear();
830    }
831}
832#[allow(dead_code)]
833pub struct HskPassRegistry {
834    pub(super) configs: Vec<HskPassConfig>,
835    pub(super) stats: std::collections::HashMap<String, HskPassStats>,
836}
837impl HskPassRegistry {
838    #[allow(dead_code)]
839    pub fn new() -> Self {
840        HskPassRegistry {
841            configs: Vec::new(),
842            stats: std::collections::HashMap::new(),
843        }
844    }
845    #[allow(dead_code)]
846    pub fn register(&mut self, config: HskPassConfig) {
847        self.stats
848            .insert(config.pass_name.clone(), HskPassStats::new());
849        self.configs.push(config);
850    }
851    #[allow(dead_code)]
852    pub fn enabled_passes(&self) -> Vec<&HskPassConfig> {
853        self.configs.iter().filter(|c| c.enabled).collect()
854    }
855    #[allow(dead_code)]
856    pub fn get_stats(&self, name: &str) -> Option<&HskPassStats> {
857        self.stats.get(name)
858    }
859    #[allow(dead_code)]
860    pub fn total_passes(&self) -> usize {
861        self.configs.len()
862    }
863    #[allow(dead_code)]
864    pub fn enabled_count(&self) -> usize {
865        self.enabled_passes().len()
866    }
867    #[allow(dead_code)]
868    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
869        if let Some(stats) = self.stats.get_mut(name) {
870            stats.record_run(changes, time_ms, iter);
871        }
872    }
873}
874#[allow(dead_code)]
875#[derive(Debug, Clone, PartialEq)]
876pub enum HskPassPhase {
877    Analysis,
878    Transformation,
879    Verification,
880    Cleanup,
881}
882impl HskPassPhase {
883    #[allow(dead_code)]
884    pub fn name(&self) -> &str {
885        match self {
886            HskPassPhase::Analysis => "analysis",
887            HskPassPhase::Transformation => "transformation",
888            HskPassPhase::Verification => "verification",
889            HskPassPhase::Cleanup => "cleanup",
890        }
891    }
892    #[allow(dead_code)]
893    pub fn is_modifying(&self) -> bool {
894        matches!(self, HskPassPhase::Transformation | HskPassPhase::Cleanup)
895    }
896}
897/// A Haskell function definition (possibly with multiple equations).
898///
899/// Example:
900/// ```text
901/// factorial :: Int -> Int
902/// factorial 0 = 1
903/// factorial n = n * factorial (n - 1)
904/// ```
905#[derive(Debug, Clone, PartialEq)]
906pub struct HaskellFunction {
907    /// Function name
908    pub name: String,
909    /// Type signature annotation
910    pub type_annotation: Option<HaskellType>,
911    /// Equations (pattern-matched clauses)
912    pub equations: Vec<HaskellEquation>,
913}
914/// Tracks declared names for HsExt scope analysis.
915#[derive(Debug, Default)]
916pub struct HsExtNameScope {
917    pub(super) declared: std::collections::HashSet<String>,
918    pub(super) depth: usize,
919    pub(super) parent: Option<Box<HsExtNameScope>>,
920}
921impl HsExtNameScope {
922    pub fn new() -> Self {
923        HsExtNameScope::default()
924    }
925    pub fn declare(&mut self, name: impl Into<String>) -> bool {
926        self.declared.insert(name.into())
927    }
928    pub fn is_declared(&self, name: &str) -> bool {
929        self.declared.contains(name)
930    }
931    pub fn push_scope(self) -> Self {
932        HsExtNameScope {
933            declared: std::collections::HashSet::new(),
934            depth: self.depth + 1,
935            parent: Some(Box::new(self)),
936        }
937    }
938    pub fn pop_scope(self) -> Self {
939        *self.parent.unwrap_or_default()
940    }
941    pub fn depth(&self) -> usize {
942        self.depth
943    }
944    pub fn len(&self) -> usize {
945        self.declared.len()
946    }
947}
948#[allow(dead_code)]
949#[derive(Debug, Clone)]
950pub struct HskCacheEntry {
951    pub key: String,
952    pub data: Vec<u8>,
953    pub timestamp: u64,
954    pub valid: bool,
955}
956/// Haskell pattern AST used in `case` and function equations.
957#[derive(Debug, Clone, PartialEq)]
958pub enum HaskellPattern {
959    /// `_` — wildcard
960    Wildcard,
961    /// Variable binding: `x`
962    Var(String),
963    /// Literal pattern: `42`, `'a'`, `True`
964    Lit(HaskellLit),
965    /// Tuple pattern: `(a, b, c)`
966    Tuple(Vec<HaskellPattern>),
967    /// List pattern: `[a, b, c]`
968    List(Vec<HaskellPattern>),
969    /// Cons pattern: `x : xs`
970    Cons(Box<HaskellPattern>, Box<HaskellPattern>),
971    /// Constructor pattern: `Just x`, `Left a`
972    Constructor(String, Vec<HaskellPattern>),
973    /// As pattern: `xs@(x:rest)`
974    As(String, Box<HaskellPattern>),
975    /// Lazy (irrefutable) pattern: `~pat`
976    LazyPat(Box<HaskellPattern>),
977}
978/// Haskell expression AST.
979#[derive(Debug, Clone, PartialEq)]
980pub enum HaskellExpr {
981    /// Literal value
982    Lit(HaskellLit),
983    /// Variable or constructor reference: `foo`, `Just`, `(:)`
984    Var(String),
985    /// Function application: `f x y`
986    App(Box<HaskellExpr>, Vec<HaskellExpr>),
987    /// Lambda: `\x y -> body`
988    Lambda(Vec<HaskellPattern>, Box<HaskellExpr>),
989    /// Let expression: `let x = e1 in e2`
990    Let(String, Box<HaskellExpr>, Box<HaskellExpr>),
991    /// Where clause as an expression wrapper
992    Where(Box<HaskellExpr>, Vec<HaskellFunction>),
993    /// If-then-else: `if c then t else e`
994    If(Box<HaskellExpr>, Box<HaskellExpr>, Box<HaskellExpr>),
995    /// Case expression
996    Case(Box<HaskellExpr>, Vec<HaskellCaseAlt>),
997    /// Do notation block
998    Do(Vec<HaskellDoStmt>),
999    /// List comprehension: `[ body | quals ]`
1000    ListComp(Box<HaskellExpr>, Vec<HsListQual>),
1001    /// Tuple: `(a, b, c)`
1002    Tuple(Vec<HaskellExpr>),
1003    /// List: `[a, b, c]`
1004    List(Vec<HaskellExpr>),
1005    /// Arithmetic negation: `negate x`
1006    Neg(Box<HaskellExpr>),
1007    /// Infix application: `x `op` y` or `x + y`
1008    InfixApp(Box<HaskellExpr>, String, Box<HaskellExpr>),
1009    /// Operator section / raw operator reference: `(+)`, `map`
1010    Operator(String),
1011    /// Type annotation: `expr :: Type`
1012    TypeAnnotation(Box<HaskellExpr>, HaskellType),
1013}
1014/// A Haskell `class` declaration.
1015///
1016/// Example:
1017/// ```text
1018/// class Functor f where
1019///   fmap :: (a -> b) -> f a -> f b
1020/// ```
1021#[derive(Debug, Clone, PartialEq)]
1022pub struct HaskellTypeClass {
1023    /// Class name: `Functor`
1024    pub name: String,
1025    /// Class type parameters: `f`
1026    pub type_params: Vec<String>,
1027    /// Superclass constraints: `Eq a`, `Ord a`
1028    pub superclasses: Vec<HaskellType>,
1029    /// Methods: (name, signature, optional default body)
1030    pub methods: Vec<(String, HaskellType, Option<HaskellExpr>)>,
1031}
1032/// Haskell type representation for type-directed code generation.
1033#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1034pub enum HaskellType {
1035    /// `Int` — fixed-precision signed integer (at least 30-bit)
1036    Int,
1037    /// `Integer` — arbitrary-precision integer
1038    Integer,
1039    /// `Double` — 64-bit IEEE floating-point
1040    Double,
1041    /// `Float` — 32-bit IEEE floating-point
1042    Float,
1043    /// `Bool` — boolean
1044    Bool,
1045    /// `Char` — Unicode character
1046    Char,
1047    /// `String` — alias for `[Char]`
1048    HsString,
1049    /// `()` — unit type
1050    Unit,
1051    /// `IO a` — IO monad
1052    IO(Box<HaskellType>),
1053    /// `[a]` — list of a
1054    List(Box<HaskellType>),
1055    /// `Maybe a` — optional value
1056    Maybe(Box<HaskellType>),
1057    /// `Either a b` — sum type
1058    Either(Box<HaskellType>, Box<HaskellType>),
1059    /// `(a, b, ...)` — tuple
1060    Tuple(Vec<HaskellType>),
1061    /// `a -> b` — function type
1062    Fun(Box<HaskellType>, Box<HaskellType>),
1063    /// Named type (data, newtype, type synonym)
1064    Custom(String),
1065    /// Polymorphic type variable: `a`, `b`, `f`
1066    Polymorphic(String),
1067    /// Type class constraint: `Eq a`, `Functor f`
1068    Constraint(String, Vec<HaskellType>),
1069}
1070/// A single equation of a Haskell function definition.
1071///
1072/// Example: `factorial 0 = 1`
1073#[derive(Debug, Clone, PartialEq)]
1074pub struct HaskellEquation {
1075    /// Argument patterns for this equation
1076    pub patterns: Vec<HaskellPattern>,
1077    /// Optional guards; if empty, body is unconditional
1078    pub guards: Vec<HaskellGuard>,
1079    /// Right-hand side (if no guards)
1080    pub body: Option<HaskellExpr>,
1081    /// Where clause local bindings
1082    pub where_clause: Vec<HaskellFunction>,
1083}
1084/// A Haskell import declaration.
1085#[derive(Debug, Clone, PartialEq)]
1086pub struct HaskellImport {
1087    /// Module name: `Data.List`
1088    pub module: String,
1089    /// `qualified` import
1090    pub qualified: bool,
1091    /// `as` alias
1092    pub alias: Option<String>,
1093    /// Explicit import list (empty = import everything)
1094    pub items: Vec<String>,
1095    /// `hiding` list
1096    pub hiding: Vec<String>,
1097}
1098/// A diagnostic message from a HsExt pass.
1099#[derive(Debug, Clone)]
1100pub struct HsExtDiagMsg {
1101    pub severity: HsExtDiagSeverity,
1102    pub pass: String,
1103    pub message: String,
1104}
1105impl HsExtDiagMsg {
1106    pub fn error(pass: impl Into<String>, msg: impl Into<String>) -> Self {
1107        HsExtDiagMsg {
1108            severity: HsExtDiagSeverity::Error,
1109            pass: pass.into(),
1110            message: msg.into(),
1111        }
1112    }
1113    pub fn warning(pass: impl Into<String>, msg: impl Into<String>) -> Self {
1114        HsExtDiagMsg {
1115            severity: HsExtDiagSeverity::Warning,
1116            pass: pass.into(),
1117            message: msg.into(),
1118        }
1119    }
1120    pub fn note(pass: impl Into<String>, msg: impl Into<String>) -> Self {
1121        HsExtDiagMsg {
1122            severity: HsExtDiagSeverity::Note,
1123            pass: pass.into(),
1124            message: msg.into(),
1125        }
1126    }
1127}
1128/// Collects HsExt diagnostics.
1129#[derive(Debug, Default)]
1130pub struct HsExtDiagCollector {
1131    pub(super) msgs: Vec<HsExtDiagMsg>,
1132}
1133impl HsExtDiagCollector {
1134    pub fn new() -> Self {
1135        HsExtDiagCollector::default()
1136    }
1137    pub fn emit(&mut self, d: HsExtDiagMsg) {
1138        self.msgs.push(d);
1139    }
1140    pub fn has_errors(&self) -> bool {
1141        self.msgs
1142            .iter()
1143            .any(|d| d.severity == HsExtDiagSeverity::Error)
1144    }
1145    pub fn errors(&self) -> Vec<&HsExtDiagMsg> {
1146        self.msgs
1147            .iter()
1148            .filter(|d| d.severity == HsExtDiagSeverity::Error)
1149            .collect()
1150    }
1151    pub fn warnings(&self) -> Vec<&HsExtDiagMsg> {
1152        self.msgs
1153            .iter()
1154            .filter(|d| d.severity == HsExtDiagSeverity::Warning)
1155            .collect()
1156    }
1157    pub fn len(&self) -> usize {
1158        self.msgs.len()
1159    }
1160    pub fn is_empty(&self) -> bool {
1161        self.msgs.is_empty()
1162    }
1163    pub fn clear(&mut self) {
1164        self.msgs.clear();
1165    }
1166}
1167#[allow(dead_code)]
1168#[derive(Debug, Clone)]
1169pub struct HskAnalysisCache {
1170    pub(super) entries: std::collections::HashMap<String, HskCacheEntry>,
1171    pub(super) max_size: usize,
1172    pub(super) hits: u64,
1173    pub(super) misses: u64,
1174}
1175impl HskAnalysisCache {
1176    #[allow(dead_code)]
1177    pub fn new(max_size: usize) -> Self {
1178        HskAnalysisCache {
1179            entries: std::collections::HashMap::new(),
1180            max_size,
1181            hits: 0,
1182            misses: 0,
1183        }
1184    }
1185    #[allow(dead_code)]
1186    pub fn get(&mut self, key: &str) -> Option<&HskCacheEntry> {
1187        if self.entries.contains_key(key) {
1188            self.hits += 1;
1189            self.entries.get(key)
1190        } else {
1191            self.misses += 1;
1192            None
1193        }
1194    }
1195    #[allow(dead_code)]
1196    pub fn insert(&mut self, key: String, data: Vec<u8>) {
1197        if self.entries.len() >= self.max_size {
1198            if let Some(oldest) = self.entries.keys().next().cloned() {
1199                self.entries.remove(&oldest);
1200            }
1201        }
1202        self.entries.insert(
1203            key.clone(),
1204            HskCacheEntry {
1205                key,
1206                data,
1207                timestamp: 0,
1208                valid: true,
1209            },
1210        );
1211    }
1212    #[allow(dead_code)]
1213    pub fn invalidate(&mut self, key: &str) {
1214        if let Some(entry) = self.entries.get_mut(key) {
1215            entry.valid = false;
1216        }
1217    }
1218    #[allow(dead_code)]
1219    pub fn clear(&mut self) {
1220        self.entries.clear();
1221    }
1222    #[allow(dead_code)]
1223    pub fn hit_rate(&self) -> f64 {
1224        let total = self.hits + self.misses;
1225        if total == 0 {
1226            return 0.0;
1227        }
1228        self.hits as f64 / total as f64
1229    }
1230    #[allow(dead_code)]
1231    pub fn size(&self) -> usize {
1232        self.entries.len()
1233    }
1234}
1235#[allow(dead_code)]
1236#[derive(Debug, Clone)]
1237pub struct HskDepGraph {
1238    pub(super) nodes: Vec<u32>,
1239    pub(super) edges: Vec<(u32, u32)>,
1240}
1241impl HskDepGraph {
1242    #[allow(dead_code)]
1243    pub fn new() -> Self {
1244        HskDepGraph {
1245            nodes: Vec::new(),
1246            edges: Vec::new(),
1247        }
1248    }
1249    #[allow(dead_code)]
1250    pub fn add_node(&mut self, id: u32) {
1251        if !self.nodes.contains(&id) {
1252            self.nodes.push(id);
1253        }
1254    }
1255    #[allow(dead_code)]
1256    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1257        self.add_node(dep);
1258        self.add_node(dependent);
1259        self.edges.push((dep, dependent));
1260    }
1261    #[allow(dead_code)]
1262    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1263        self.edges
1264            .iter()
1265            .filter(|(d, _)| *d == node)
1266            .map(|(_, dep)| *dep)
1267            .collect()
1268    }
1269    #[allow(dead_code)]
1270    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1271        self.edges
1272            .iter()
1273            .filter(|(_, dep)| *dep == node)
1274            .map(|(d, _)| *d)
1275            .collect()
1276    }
1277    #[allow(dead_code)]
1278    pub fn topological_sort(&self) -> Vec<u32> {
1279        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1280        for &n in &self.nodes {
1281            in_degree.insert(n, 0);
1282        }
1283        for (_, dep) in &self.edges {
1284            *in_degree.entry(*dep).or_insert(0) += 1;
1285        }
1286        let mut queue: std::collections::VecDeque<u32> = self
1287            .nodes
1288            .iter()
1289            .filter(|&&n| in_degree[&n] == 0)
1290            .copied()
1291            .collect();
1292        let mut result = Vec::new();
1293        while let Some(node) = queue.pop_front() {
1294            result.push(node);
1295            for dep in self.dependents_of(node) {
1296                let cnt = in_degree.entry(dep).or_insert(0);
1297                *cnt = cnt.saturating_sub(1);
1298                if *cnt == 0 {
1299                    queue.push_back(dep);
1300                }
1301            }
1302        }
1303        result
1304    }
1305    #[allow(dead_code)]
1306    pub fn has_cycle(&self) -> bool {
1307        self.topological_sort().len() < self.nodes.len()
1308    }
1309}
1310/// Pipeline profiler for HsExt.
1311#[derive(Debug, Default)]
1312pub struct HsExtProfiler {
1313    pub(super) timings: Vec<HsExtPassTiming>,
1314}
1315impl HsExtProfiler {
1316    pub fn new() -> Self {
1317        HsExtProfiler::default()
1318    }
1319    pub fn record(&mut self, t: HsExtPassTiming) {
1320        self.timings.push(t);
1321    }
1322    pub fn total_elapsed_us(&self) -> u64 {
1323        self.timings.iter().map(|t| t.elapsed_us).sum()
1324    }
1325    pub fn slowest_pass(&self) -> Option<&HsExtPassTiming> {
1326        self.timings.iter().max_by_key(|t| t.elapsed_us)
1327    }
1328    pub fn num_passes(&self) -> usize {
1329        self.timings.len()
1330    }
1331    pub fn profitable_passes(&self) -> Vec<&HsExtPassTiming> {
1332        self.timings.iter().filter(|t| t.is_profitable()).collect()
1333    }
1334}
1335/// Pass-timing record for HsExt profiler.
1336#[derive(Debug, Clone)]
1337pub struct HsExtPassTiming {
1338    pub pass_name: String,
1339    pub elapsed_us: u64,
1340    pub items_processed: usize,
1341    pub bytes_before: usize,
1342    pub bytes_after: usize,
1343}
1344impl HsExtPassTiming {
1345    pub fn new(
1346        pass_name: impl Into<String>,
1347        elapsed_us: u64,
1348        items: usize,
1349        before: usize,
1350        after: usize,
1351    ) -> Self {
1352        HsExtPassTiming {
1353            pass_name: pass_name.into(),
1354            elapsed_us,
1355            items_processed: items,
1356            bytes_before: before,
1357            bytes_after: after,
1358        }
1359    }
1360    pub fn throughput_mps(&self) -> f64 {
1361        if self.elapsed_us == 0 {
1362            0.0
1363        } else {
1364            self.items_processed as f64 / (self.elapsed_us as f64 / 1_000_000.0)
1365        }
1366    }
1367    pub fn size_ratio(&self) -> f64 {
1368        if self.bytes_before == 0 {
1369            1.0
1370        } else {
1371            self.bytes_after as f64 / self.bytes_before as f64
1372        }
1373    }
1374    pub fn is_profitable(&self) -> bool {
1375        self.size_ratio() <= 1.05
1376    }
1377}
1378/// A monotonically increasing ID generator for HsExt.
1379#[derive(Debug, Default)]
1380pub struct HsExtIdGen {
1381    pub(super) next: u32,
1382}
1383impl HsExtIdGen {
1384    pub fn new() -> Self {
1385        HsExtIdGen::default()
1386    }
1387    pub fn next_id(&mut self) -> u32 {
1388        let id = self.next;
1389        self.next += 1;
1390        id
1391    }
1392    pub fn peek_next(&self) -> u32 {
1393        self.next
1394    }
1395    pub fn reset(&mut self) {
1396        self.next = 0;
1397    }
1398    pub fn skip(&mut self, n: u32) {
1399        self.next += n;
1400    }
1401}
1402/// Severity of a HsExt diagnostic.
1403#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1404pub enum HsExtDiagSeverity {
1405    Note,
1406    Warning,
1407    Error,
1408}
1409/// A generic key-value configuration store for HsExt.
1410#[derive(Debug, Clone, Default)]
1411pub struct HsExtConfig {
1412    pub(super) entries: std::collections::HashMap<String, String>,
1413}
1414impl HsExtConfig {
1415    pub fn new() -> Self {
1416        HsExtConfig::default()
1417    }
1418    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
1419        self.entries.insert(key.into(), value.into());
1420    }
1421    pub fn get(&self, key: &str) -> Option<&str> {
1422        self.entries.get(key).map(|s| s.as_str())
1423    }
1424    pub fn get_bool(&self, key: &str) -> bool {
1425        matches!(self.get(key), Some("true") | Some("1") | Some("yes"))
1426    }
1427    pub fn get_int(&self, key: &str) -> Option<i64> {
1428        self.get(key)?.parse().ok()
1429    }
1430    pub fn len(&self) -> usize {
1431        self.entries.len()
1432    }
1433    pub fn is_empty(&self) -> bool {
1434        self.entries.is_empty()
1435    }
1436}