Skip to main content

oxilean_codegen/beam_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::collections::HashMap;
7
8use super::functions::*;
9use std::collections::HashSet;
10
11/// A BEAM value operand (register or immediate).
12#[derive(Debug, Clone)]
13pub enum BeamVal {
14    /// A register
15    Reg(BeamReg),
16    /// Integer immediate
17    Int(i64),
18    /// Float immediate
19    Float(f64),
20    /// Atom immediate
21    Atom(String),
22    /// Nil immediate
23    Nil,
24    /// Literal term from literal pool
25    Literal(u32),
26}
27/// Represents a BEAM process (actor) in the actor model.
28///
29/// BEAM processes are lightweight and communicate via asynchronous message
30/// passing through mailboxes.
31#[allow(dead_code)]
32#[derive(Debug, Clone)]
33pub struct BeamProcess {
34    /// Unique process identifier (symbolic)
35    pub pid_sym: String,
36    /// Module where this process is defined
37    pub module: String,
38    /// Startup function name
39    pub init_fn: String,
40    /// Initial arguments
41    pub init_args: Vec<BeamExpr>,
42    /// Whether this process is linked (crashes propagate)
43    pub linked: bool,
44    /// Whether this process is monitored
45    pub monitored: bool,
46    /// Process dictionary entries (name → value)
47    pub dictionary: Vec<(String, BeamExpr)>,
48    /// Trap-exit flag: catches EXIT signals as messages
49    pub trap_exit: bool,
50}
51impl BeamProcess {
52    /// Create a new process description.
53    #[allow(dead_code)]
54    pub fn new(
55        pid_sym: impl Into<String>,
56        module: impl Into<String>,
57        init_fn: impl Into<String>,
58    ) -> Self {
59        BeamProcess {
60            pid_sym: pid_sym.into(),
61            module: module.into(),
62            init_fn: init_fn.into(),
63            init_args: Vec::new(),
64            linked: false,
65            monitored: false,
66            dictionary: Vec::new(),
67            trap_exit: false,
68        }
69    }
70    /// Add an initial argument.
71    #[allow(dead_code)]
72    pub fn with_arg(mut self, arg: BeamExpr) -> Self {
73        self.init_args.push(arg);
74        self
75    }
76    /// Mark the process as linked.
77    #[allow(dead_code)]
78    pub fn linked(mut self) -> Self {
79        self.linked = true;
80        self
81    }
82    /// Enable trap_exit.
83    #[allow(dead_code)]
84    pub fn trap_exit(mut self) -> Self {
85        self.trap_exit = true;
86        self
87    }
88    /// Emit the `spawn/3` call that creates this process.
89    #[allow(dead_code)]
90    pub fn emit_spawn(&self) -> BeamExpr {
91        let args_list = self
92            .init_args
93            .iter()
94            .cloned()
95            .fold(BeamExpr::Nil, |tail, head| {
96                BeamExpr::Cons(Box::new(head), Box::new(tail))
97            });
98        let spawn_fn = if self.linked { "spawn_link" } else { "spawn" };
99        BeamExpr::Call {
100            module: Some("erlang".to_string()),
101            func: spawn_fn.to_string(),
102            args: vec![
103                BeamExpr::LitAtom(self.module.clone()),
104                BeamExpr::LitAtom(self.init_fn.clone()),
105                args_list,
106            ],
107        }
108    }
109}
110/// ETS table access permissions.
111#[allow(dead_code)]
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum EtsAccess {
114    /// Only owner can read/write
115    Private,
116    /// All can read, owner can write
117    Protected,
118    /// All can read and write
119    Public,
120}
121/// Description of an ETS table.
122#[allow(dead_code)]
123#[derive(Debug, Clone)]
124pub struct EtsTable {
125    /// Table name (atom)
126    pub name: String,
127    /// Table type
128    pub table_type: EtsType,
129    /// Access permissions
130    pub access: EtsAccess,
131    /// Whether the table is named (accessible by name instead of ref)
132    pub named_table: bool,
133    /// Key position (1-indexed tuple position)
134    pub key_pos: u32,
135}
136impl EtsTable {
137    /// Create a new named public ETS set table.
138    #[allow(dead_code)]
139    pub fn new_set(name: impl Into<String>) -> Self {
140        EtsTable {
141            name: name.into(),
142            table_type: EtsType::Set,
143            access: EtsAccess::Public,
144            named_table: true,
145            key_pos: 1,
146        }
147    }
148    /// Emit the `ets:new/2` call that creates this table.
149    #[allow(dead_code)]
150    pub fn emit_new(&self) -> BeamExpr {
151        let mut opts = vec![BeamExpr::LitAtom(self.table_type.to_string())];
152        if self.named_table {
153            opts.push(BeamExpr::LitAtom("named_table".to_string()));
154        }
155        opts.push(BeamExpr::LitAtom(self.access.to_string()));
156        if self.key_pos != 1 {
157            opts.push(BeamExpr::Tuple(vec![
158                BeamExpr::LitAtom("keypos".to_string()),
159                BeamExpr::LitInt(self.key_pos as i64),
160            ]));
161        }
162        let opts_list = opts.into_iter().fold(BeamExpr::Nil, |tail, head| {
163            BeamExpr::Cons(Box::new(head), Box::new(tail))
164        });
165        BeamExpr::Call {
166            module: Some("ets".to_string()),
167            func: "new".to_string(),
168            args: vec![BeamExpr::LitAtom(self.name.clone()), opts_list],
169        }
170    }
171    /// Emit `ets:insert/2` call.
172    #[allow(dead_code)]
173    pub fn emit_insert(&self, tuple: BeamExpr) -> BeamExpr {
174        BeamExpr::Call {
175            module: Some("ets".to_string()),
176            func: "insert".to_string(),
177            args: vec![BeamExpr::LitAtom(self.name.clone()), tuple],
178        }
179    }
180    /// Emit `ets:lookup/2` call.
181    #[allow(dead_code)]
182    pub fn emit_lookup(&self, key: BeamExpr) -> BeamExpr {
183        BeamExpr::Call {
184            module: Some("ets".to_string()),
185            func: "lookup".to_string(),
186            args: vec![BeamExpr::LitAtom(self.name.clone()), key],
187        }
188    }
189    /// Emit `ets:delete/2` call.
190    #[allow(dead_code)]
191    pub fn emit_delete(&self, key: BeamExpr) -> BeamExpr {
192        BeamExpr::Call {
193            module: Some("ets".to_string()),
194            func: "delete".to_string(),
195            args: vec![BeamExpr::LitAtom(self.name.clone()), key],
196        }
197    }
198}
199/// Result of tail-call analysis for a BEAM function.
200#[allow(dead_code)]
201#[derive(Debug, Clone, Default)]
202pub struct TailCallInfo {
203    /// Number of self-recursive tail calls found
204    pub self_tail_calls: u32,
205    /// Names of external functions called in tail position
206    pub external_tails: Vec<String>,
207    /// Whether the function is tail-recursive (pure loop)
208    pub is_tail_recursive: bool,
209}
210impl TailCallInfo {
211    /// Create empty tail call info.
212    #[allow(dead_code)]
213    pub fn new() -> Self {
214        Self::default()
215    }
216    /// Record a self-recursive tail call.
217    #[allow(dead_code)]
218    pub fn add_self_tail(&mut self) {
219        self.self_tail_calls += 1;
220        self.is_tail_recursive = true;
221    }
222    /// Record an external tail call.
223    #[allow(dead_code)]
224    pub fn add_external_tail(&mut self, name: impl Into<String>) {
225        self.external_tails.push(name.into());
226    }
227    /// Returns true if there are any tail calls.
228    #[allow(dead_code)]
229    pub fn has_tail_calls(&self) -> bool {
230        self.self_tail_calls > 0 || !self.external_tails.is_empty()
231    }
232}
233/// A pattern-matching clause in a case expression.
234#[derive(Debug, Clone)]
235pub struct BeamClause {
236    /// Pattern to match against
237    pub pattern: BeamPattern,
238    /// Optional guard expression
239    pub guard: Option<BeamExpr>,
240    /// Body expression evaluated on match
241    pub body: BeamExpr,
242}
243/// Context for emitting BEAM / Core Erlang code.
244pub struct BeamEmitCtx {
245    /// Current label counter
246    pub(super) label_counter: u32,
247    /// Current variable counter (for fresh names)
248    pub(super) var_counter: u32,
249    /// Indent level for pretty-printing
250    pub(super) indent: usize,
251    /// Output buffer
252    pub(super) output: String,
253}
254impl BeamEmitCtx {
255    pub fn new() -> Self {
256        BeamEmitCtx {
257            label_counter: 1,
258            var_counter: 0,
259            indent: 0,
260            output: String::new(),
261        }
262    }
263    pub(super) fn fresh_label(&mut self) -> u32 {
264        let l = self.label_counter;
265        self.label_counter += 1;
266        l
267    }
268    pub(super) fn fresh_var(&mut self) -> String {
269        let v = self.var_counter;
270        self.var_counter += 1;
271        format!("_V{}", v)
272    }
273    pub(super) fn indent_str(&self) -> String {
274        "  ".repeat(self.indent)
275    }
276    pub(super) fn emit_line(&mut self, line: &str) {
277        let indent = self.indent_str();
278        self.output.push_str(&indent);
279        self.output.push_str(line);
280        self.output.push('\n');
281    }
282    pub(super) fn indented<F: FnOnce(&mut Self)>(&mut self, f: F) {
283        self.indent += 1;
284        f(self);
285        self.indent -= 1;
286    }
287}
288/// Normalizes BEAM patterns for comparison and deduplication.
289#[allow(dead_code)]
290#[derive(Debug, Clone, Default)]
291pub struct PatternNormalizer {
292    /// Counter for generating fresh wildcard names
293    pub(super) wildcard_counter: u32,
294}
295impl PatternNormalizer {
296    /// Create a new normalizer.
297    #[allow(dead_code)]
298    pub fn new() -> Self {
299        Self::default()
300    }
301    /// Normalize a pattern (replace all variables with canonical names).
302    #[allow(dead_code)]
303    pub fn normalize(&mut self, pat: BeamPattern) -> BeamPattern {
304        match pat {
305            BeamPattern::Var(_) => {
306                let n = self.wildcard_counter;
307                self.wildcard_counter += 1;
308                BeamPattern::Var(format!("_N{}", n))
309            }
310            BeamPattern::Alias(_, inner) => self.normalize(*inner),
311            BeamPattern::Cons(h, t) => {
312                let hn = self.normalize(*h);
313                let tn = self.normalize(*t);
314                BeamPattern::Cons(Box::new(hn), Box::new(tn))
315            }
316            BeamPattern::Tuple(pats) => {
317                BeamPattern::Tuple(pats.into_iter().map(|p| self.normalize(p)).collect())
318            }
319            other => other,
320        }
321    }
322    /// Check whether two patterns are structurally equivalent after normalization.
323    #[allow(dead_code)]
324    pub fn equivalent(&mut self, a: BeamPattern, b: BeamPattern) -> bool {
325        let na = self.normalize(a);
326        let nb = self.normalize(b);
327        patterns_structurally_equal(&na, &nb)
328    }
329}
330/// BEAM VM type representation.
331///
332/// BEAM is dynamically typed; these types are used for documentation
333/// and static analysis purposes within the code generator.
334#[derive(Debug, Clone, PartialEq, Eq, Hash)]
335pub enum BeamType {
336    /// Fixed-precision integer (maps to Erlang `integer()`)
337    Integer,
338    /// Floating-point number (maps to Erlang `float()`)
339    Float,
340    /// Atom (interned symbol, maps to Erlang `atom()`)
341    Atom,
342    /// Process identifier (maps to Erlang `pid()`)
343    Pid,
344    /// Port identifier (maps to Erlang `port()`)
345    Port,
346    /// Unique reference (maps to Erlang `reference()`)
347    Reference,
348    /// Binary data (maps to Erlang `binary()`)
349    Binary,
350    /// Linked list (maps to Erlang `list()`)
351    List(Box<BeamType>),
352    /// Heterogeneous tuple (maps to Erlang `tuple()`)
353    Tuple(Vec<BeamType>),
354    /// Key-value map (maps to Erlang `map()`)
355    Map(Box<BeamType>, Box<BeamType>),
356    /// First-class function value (maps to Erlang `fun()`)
357    Fun(Vec<BeamType>, Box<BeamType>),
358    /// Any type (Erlang `any()`)
359    Any,
360    /// No return type (Erlang `none()`)
361    None,
362    /// Union of types
363    Union(Vec<BeamType>),
364    /// Named type alias or user-defined type
365    Named(String),
366}
367/// A function in a BEAM module.
368#[derive(Debug, Clone)]
369pub struct BeamFunction {
370    /// Function name (atom)
371    pub name: String,
372    /// Number of formal parameters
373    pub arity: usize,
374    /// Core Erlang style clauses (pattern, guard, body)
375    pub clauses: Vec<BeamClause>,
376    /// Optional parameter names (for documentation)
377    pub params: Vec<String>,
378    /// Key-value annotations (e.g., `{file, "foo.erl"}`)
379    pub annotations: Vec<(String, String)>,
380    /// Whether this function is exported
381    pub exported: bool,
382    /// Low-level instruction sequence (populated by lowering pass)
383    pub instrs: Vec<BeamInstr>,
384    /// Number of Y-register (stack) slots needed
385    pub frame_size: u32,
386    /// Return type annotation
387    pub return_type: Option<BeamType>,
388    /// Parameter type annotations
389    pub param_types: Vec<BeamType>,
390}
391impl BeamFunction {
392    /// Create a new function with the given name and arity.
393    pub fn new(name: impl Into<String>, arity: usize) -> Self {
394        BeamFunction {
395            name: name.into(),
396            arity,
397            clauses: Vec::new(),
398            params: Vec::new(),
399            annotations: Vec::new(),
400            exported: false,
401            instrs: Vec::new(),
402            frame_size: 0,
403            return_type: None,
404            param_types: Vec::new(),
405        }
406    }
407    /// Add a clause to this function.
408    pub fn add_clause(&mut self, clause: BeamClause) {
409        self.clauses.push(clause);
410    }
411    /// Add an annotation.
412    pub fn annotate(&mut self, key: impl Into<String>, value: impl Into<String>) {
413        self.annotations.push((key.into(), value.into()));
414    }
415    /// Mark as exported.
416    pub fn export(&mut self) {
417        self.exported = true;
418    }
419    /// Get the function's name/arity key.
420    pub fn key(&self) -> String {
421        format!("{}/{}", self.name, self.arity)
422    }
423}
424/// Eliminates unexported, unreachable functions from a BEAM module.
425#[allow(dead_code)]
426#[derive(Debug, Clone, Default)]
427pub struct BeamDeadEliminator {
428    /// Set of known reachable function names
429    pub(super) reachable: std::collections::HashSet<String>,
430}
431impl BeamDeadEliminator {
432    /// Create a new eliminator.
433    #[allow(dead_code)]
434    pub fn new() -> Self {
435        Self::default()
436    }
437    /// Mark all exported functions as reachable entry points.
438    #[allow(dead_code)]
439    pub fn seed_exports(&mut self, module: &BeamModule) {
440        for (name, arity) in &module.exports {
441            self.reachable.insert(format!("{}/{}", name, arity));
442        }
443    }
444    /// Eliminate dead (unreachable) functions from the module.
445    #[allow(dead_code)]
446    pub fn eliminate(&self, module: BeamModule) -> BeamModule {
447        let mut result = BeamModule::new(module.name.clone());
448        result.attributes = module.attributes;
449        result.exports = module.exports;
450        result.on_load = module.on_load;
451        result.compile_info = module.compile_info;
452        for func in module.functions {
453            let key = format!("{}/{}", func.name, func.arity);
454            if self.reachable.contains(&key) || func.exported {
455                result.functions.push(func);
456            }
457        }
458        result
459    }
460    /// Number of eliminated functions from a before/after comparison.
461    #[allow(dead_code)]
462    pub fn eliminated_count(before: &BeamModule, after: &BeamModule) -> usize {
463        before.functions.len().saturating_sub(after.functions.len())
464    }
465}
466/// Helper for building Erlang module attributes.
467#[allow(dead_code)]
468#[derive(Debug, Clone, Default)]
469pub struct AttributeBuilder {
470    pub(super) attrs: Vec<(String, String)>,
471}
472impl AttributeBuilder {
473    /// Create a new attribute builder.
474    #[allow(dead_code)]
475    pub fn new() -> Self {
476        Self::default()
477    }
478    /// Add a `vsn` attribute (module version).
479    #[allow(dead_code)]
480    pub fn vsn(mut self, version: impl Into<String>) -> Self {
481        self.attrs.push(("vsn".into(), version.into()));
482        self
483    }
484    /// Add a `author` attribute.
485    #[allow(dead_code)]
486    pub fn author(mut self, name: impl Into<String>) -> Self {
487        self.attrs.push(("author".into(), name.into()));
488        self
489    }
490    /// Add a `compile` attribute (e.g., `{compile, [debug_info]}`).
491    #[allow(dead_code)]
492    pub fn compile(mut self, option: impl Into<String>) -> Self {
493        self.attrs.push(("compile".into(), option.into()));
494        self
495    }
496    /// Add a custom attribute.
497    #[allow(dead_code)]
498    pub fn custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
499        self.attrs.push((key.into(), value.into()));
500        self
501    }
502    /// Build the attributes vector.
503    #[allow(dead_code)]
504    pub fn build(self) -> Vec<(String, String)> {
505        self.attrs
506    }
507    /// Apply all attributes to a module.
508    #[allow(dead_code)]
509    pub fn apply(self, module: &mut BeamModule) {
510        for (k, v) in self.build() {
511            module.add_attribute(k, v);
512        }
513    }
514}
515/// Patterns for BEAM case expressions.
516#[derive(Debug, Clone)]
517pub enum BeamPattern {
518    /// Wildcard: matches anything, binds nothing
519    Wildcard,
520    /// Variable binding: matches anything, binds to name
521    Var(String),
522    /// Literal integer
523    LitInt(i64),
524    /// Literal atom
525    LitAtom(String),
526    /// Literal string
527    LitString(String),
528    /// Nil pattern `[]`
529    Nil,
530    /// Cons pattern `[Head | Tail]`
531    Cons(Box<BeamPattern>, Box<BeamPattern>),
532    /// Tuple pattern `{P1, P2, ...}`
533    Tuple(Vec<BeamPattern>),
534    /// Map pattern `#{Key := Var, ...}`
535    MapPat(Vec<(BeamExpr, BeamPattern)>),
536    /// Alias pattern `Var = Pattern`
537    Alias(String, Box<BeamPattern>),
538}
539/// Endianness for binary segments.
540#[derive(Debug, Clone, PartialEq, Eq)]
541pub enum BeamEndian {
542    Big,
543    Little,
544    Native,
545}
546/// A lightweight type-checking context for BEAM expressions.
547///
548/// This performs best-effort type inference for documentation purposes.
549#[allow(dead_code)]
550#[derive(Debug, Clone, Default)]
551pub struct BeamTypeCtx {
552    /// Variable type environment
553    pub(super) env: HashMap<String, BeamType>,
554    /// Known function types
555    pub(super) fun_types: HashMap<String, (Vec<BeamType>, BeamType)>,
556}
557impl BeamTypeCtx {
558    /// Create an empty type context.
559    #[allow(dead_code)]
560    pub fn new() -> Self {
561        Self::default()
562    }
563    /// Bind a variable to a type.
564    #[allow(dead_code)]
565    pub fn bind(&mut self, var: impl Into<String>, ty: BeamType) {
566        self.env.insert(var.into(), ty);
567    }
568    /// Look up the type of a variable.
569    #[allow(dead_code)]
570    pub fn lookup(&self, var: &str) -> Option<&BeamType> {
571        self.env.get(var)
572    }
573    /// Register a function's type signature.
574    #[allow(dead_code)]
575    pub fn register_fun(&mut self, name: impl Into<String>, params: Vec<BeamType>, ret: BeamType) {
576        self.fun_types.insert(name.into(), (params, ret));
577    }
578    /// Infer the type of a BeamExpr (returns `Any` when unknown).
579    #[allow(dead_code)]
580    pub fn infer(&self, expr: &BeamExpr) -> BeamType {
581        match expr {
582            BeamExpr::LitInt(_) => BeamType::Integer,
583            BeamExpr::LitFloat(_) => BeamType::Float,
584            BeamExpr::LitAtom(_) => BeamType::Atom,
585            BeamExpr::LitString(_) => BeamType::Named("binary".to_string()),
586            BeamExpr::Nil => BeamType::List(Box::new(BeamType::Any)),
587            BeamExpr::Var(name) => self
588                .env
589                .get(name.as_str())
590                .cloned()
591                .unwrap_or(BeamType::Any),
592            BeamExpr::Tuple(elems) => {
593                BeamType::Tuple(elems.iter().map(|e| self.infer(e)).collect())
594            }
595            BeamExpr::Cons(head, _) => BeamType::List(Box::new(self.infer(head))),
596            BeamExpr::Map(_) => BeamType::Map(Box::new(BeamType::Any), Box::new(BeamType::Any)),
597            BeamExpr::Fun { params, .. } => BeamType::Fun(
598                params.iter().map(|_| BeamType::Any).collect(),
599                Box::new(BeamType::Any),
600            ),
601            _ => BeamType::Any,
602        }
603    }
604    /// Merge another context (e.g., from two branches).
605    #[allow(dead_code)]
606    pub fn merge(&self, other: &BeamTypeCtx) -> BeamTypeCtx {
607        let mut merged = self.clone();
608        for (k, v) in &other.env {
609            if let Some(existing) = merged.env.get(k) {
610                if existing != v {
611                    merged.env.insert(k.clone(), BeamType::Any);
612                }
613            } else {
614                merged.env.insert(k.clone(), v.clone());
615            }
616        }
617        merged
618    }
619}
620/// Low-level BEAM instruction set representation.
621///
622/// These correspond to actual BEAM opcodes as documented in
623/// <https://github.com/erlang/otp/blob/master/lib/compiler/src/beam_opcodes.erl>
624#[derive(Debug, Clone)]
625pub enum BeamInstr {
626    /// `label L` — defines a jump label
627    Label(u32),
628    /// `func_info Mod Func Arity` — function header
629    FuncInfo {
630        module: String,
631        function: String,
632        arity: u32,
633    },
634    /// `call Arity Label` — local function call
635    Call { arity: u32, label: u32 },
636    /// `call_last Arity Label Deallocate` — tail call
637    CallLast {
638        arity: u32,
639        label: u32,
640        deallocate: u32,
641    },
642    /// `call_ext Arity Destination` — external call
643    CallExt {
644        arity: u32,
645        destination: BeamExtFunc,
646    },
647    /// `call_ext_last Arity Destination Deallocate` — external tail call
648    CallExtLast {
649        arity: u32,
650        destination: BeamExtFunc,
651        deallocate: u32,
652    },
653    /// `call_fun Arity` — call a fun value
654    CallFun { arity: u32 },
655    /// `move Source Destination` — move a register or literal to register
656    Move { src: BeamReg, dst: BeamReg },
657    /// `put_tuple Arity Destination` — begin tuple construction
658    PutTuple { arity: u32, dst: BeamReg },
659    /// `put Value` — add element to tuple under construction
660    Put(BeamVal),
661    /// `get_tuple_element Source Index Destination`
662    GetTupleElement {
663        src: BeamReg,
664        index: u32,
665        dst: BeamReg,
666    },
667    /// `set_tuple_element Value Tuple Index`
668    SetTupleElement {
669        value: BeamVal,
670        tuple: BeamReg,
671        index: u32,
672    },
673    /// `is_eq FailLabel Arg1 Arg2` — equality test
674    IsEq {
675        fail: u32,
676        lhs: BeamVal,
677        rhs: BeamVal,
678    },
679    /// `is_eq_exact FailLabel Arg1 Arg2` — strict equality test
680    IsEqExact {
681        fail: u32,
682        lhs: BeamVal,
683        rhs: BeamVal,
684    },
685    /// `is_ne FailLabel Arg1 Arg2` — inequality test
686    IsNe {
687        fail: u32,
688        lhs: BeamVal,
689        rhs: BeamVal,
690    },
691    /// `is_lt FailLabel Arg1 Arg2` — less-than test
692    IsLt {
693        fail: u32,
694        lhs: BeamVal,
695        rhs: BeamVal,
696    },
697    /// `is_ge FailLabel Arg1 Arg2` — greater-than-or-equal test
698    IsGe {
699        fail: u32,
700        lhs: BeamVal,
701        rhs: BeamVal,
702    },
703    /// `is_integer FailLabel Arg` — type check
704    IsInteger { fail: u32, arg: BeamVal },
705    /// `is_float FailLabel Arg`
706    IsFloat { fail: u32, arg: BeamVal },
707    /// `is_atom FailLabel Arg`
708    IsAtom { fail: u32, arg: BeamVal },
709    /// `is_nil FailLabel Arg`
710    IsNil { fail: u32, arg: BeamVal },
711    /// `is_list FailLabel Arg`
712    IsList { fail: u32, arg: BeamVal },
713    /// `is_tuple FailLabel Arg`
714    IsTuple { fail: u32, arg: BeamVal },
715    /// `is_binary FailLabel Arg`
716    IsBinary { fail: u32, arg: BeamVal },
717    /// `is_function FailLabel Arg`
718    IsFunction { fail: u32, arg: BeamVal },
719    /// `jump Label` — unconditional jump
720    Jump(u32),
721    /// `return` — return X0
722    Return,
723    /// `send` — send message: `X0 ! X1`
724    Send,
725    /// `remove_message` — consume current message
726    RemoveMessage,
727    /// `loop_rec FailLabel Destination` — receive loop
728    LoopRec { fail: u32, dst: BeamReg },
729    /// `wait Label` — wait for message
730    Wait(u32),
731    /// `wait_timeout FailLabel Time`
732    WaitTimeout { fail: u32, timeout: BeamVal },
733    /// `gc_bif Name FailLabel Live Args Destination` — garbage-collecting BIF
734    GcBif {
735        name: String,
736        fail: u32,
737        live: u32,
738        args: Vec<BeamVal>,
739        dst: BeamReg,
740    },
741    /// `bif0 Name Destination` — 0-argument BIF
742    Bif0 { name: String, dst: BeamReg },
743    /// `allocate StackNeed Live` — allocate stack frame
744    Allocate { stack_need: u32, live: u32 },
745    /// `deallocate N` — deallocate stack frame
746    Deallocate(u32),
747    /// `init Destination` — initialize to nil
748    Init(BeamReg),
749    /// `make_fun2 Index` — create closure from lambda table
750    MakeFun2(u32),
751    /// `get_list Source Head Tail` — deconstruct list
752    GetList {
753        src: BeamReg,
754        head: BeamReg,
755        tail: BeamReg,
756    },
757    /// `put_list Head Tail Destination` — construct list cell
758    PutList {
759        head: BeamVal,
760        tail: BeamVal,
761        dst: BeamReg,
762    },
763    /// `raise Class Reason` — raise exception
764    Raise { class: BeamVal, reason: BeamVal },
765    /// `try Label Register` — begin try block
766    TryBegin { label: u32, reg: BeamReg },
767    /// `try_end Register` — end try block
768    TryEnd(BeamReg),
769    /// `try_case Register` — enter catch handler
770    TryCase(BeamReg),
771    /// Raw comment for readability in dumps
772    Comment(String),
773}
774/// An external function reference (Module:Function/Arity).
775#[derive(Debug, Clone)]
776pub struct BeamExtFunc {
777    pub module: String,
778    pub function: String,
779    pub arity: u32,
780}
781/// A binary segment in a bit syntax expression.
782#[derive(Debug, Clone)]
783pub struct BeamBitSegment {
784    /// The value expression
785    pub value: BeamExpr,
786    /// Size specification (in bits)
787    pub size: Option<BeamExpr>,
788    /// Type specifier (integer, float, binary, etc.)
789    pub type_spec: String,
790    /// Signedness (signed / unsigned)
791    pub signed: bool,
792    /// Endianness (big / little / native)
793    pub endian: BeamEndian,
794    /// Unit size multiplier
795    pub unit: Option<u8>,
796}
797/// ETS table type.
798#[allow(dead_code)]
799#[derive(Debug, Clone, Copy, PartialEq, Eq)]
800pub enum EtsType {
801    /// `set` — one value per key
802    Set,
803    /// `ordered_set` — sorted, one value per key
804    OrderedSet,
805    /// `bag` — multiple values per key, duplicates allowed
806    Bag,
807    /// `duplicate_bag` — like bag but allows exact duplicates
808    DuplicateBag,
809}
810/// Core Erlang-style expression representation.
811///
812/// These map closely to Core Erlang (the intermediate language used by the
813/// Erlang compiler before generating BEAM bytecode).
814#[derive(Debug, Clone)]
815pub enum BeamExpr {
816    /// Integer literal
817    LitInt(i64),
818    /// Float literal
819    LitFloat(f64),
820    /// Atom literal (e.g., `ok`, `error`, `true`, `false`)
821    LitAtom(String),
822    /// String (represented as binary or list of chars in BEAM)
823    LitString(String),
824    /// Nil (empty list `[]`)
825    Nil,
826    /// Variable reference
827    Var(String),
828    /// Tuple constructor: `{Elem1, Elem2, ...}`
829    Tuple(Vec<BeamExpr>),
830    /// List cons: `[Head | Tail]`
831    Cons(Box<BeamExpr>, Box<BeamExpr>),
832    /// Map literal: `#{Key => Value, ...}`
833    Map(Vec<(BeamExpr, BeamExpr)>),
834    /// Map update: `Map#{Key => Value}`
835    MapUpdate(Box<BeamExpr>, Vec<(BeamExpr, BeamExpr)>),
836    /// Local function call: `Module:FuncName(Args...)`
837    Call {
838        module: Option<String>,
839        func: String,
840        args: Vec<BeamExpr>,
841    },
842    /// Inter-module call: `Module:Function(Args...)`
843    CallExt {
844        module: Box<BeamExpr>,
845        func: Box<BeamExpr>,
846        args: Vec<BeamExpr>,
847    },
848    /// Apply a fun value: `Fun(Args...)`
849    Apply {
850        fun: Box<BeamExpr>,
851        args: Vec<BeamExpr>,
852    },
853    /// Case expression for pattern matching
854    Case {
855        subject: Box<BeamExpr>,
856        clauses: Vec<BeamClause>,
857    },
858    /// Let binding: `let Var = Expr in Body`
859    Let {
860        var: String,
861        value: Box<BeamExpr>,
862        body: Box<BeamExpr>,
863    },
864    /// Letrec (mutually recursive functions)
865    Letrec {
866        bindings: Vec<(String, Vec<String>, Box<BeamExpr>)>,
867        body: Box<BeamExpr>,
868    },
869    /// Fun literal (lambda / closure)
870    Fun {
871        params: Vec<String>,
872        body: Box<BeamExpr>,
873        arity: usize,
874    },
875    /// Primitive operation (BIF — Built-In Function)
876    Primop { name: String, args: Vec<BeamExpr> },
877    /// Receive block with optional timeout
878    Receive {
879        clauses: Vec<BeamClause>,
880        timeout: Option<Box<BeamExpr>>,
881        timeout_action: Option<Box<BeamExpr>>,
882    },
883    /// Try-catch block
884    Try {
885        body: Box<BeamExpr>,
886        vars: Vec<String>,
887        handler: Box<BeamExpr>,
888        evars: Vec<String>,
889        catch_body: Box<BeamExpr>,
890    },
891    /// Sequence (do-notation style)
892    Seq(Box<BeamExpr>, Box<BeamExpr>),
893    /// Binary construction: `<<Segment, ...>>`
894    Binary(Vec<BeamBitSegment>),
895}
896/// A `gen_server` behaviour specification.
897///
898/// gen_server is the OTP generic server process behaviour.
899#[allow(dead_code)]
900#[derive(Debug, Clone)]
901pub struct GenServerSpec {
902    /// Module name
903    pub module_name: String,
904    /// Initial state type (as a comment / documentation)
905    pub state_doc: String,
906    /// Synchronous call handlers: (request_pattern, reply_expr)
907    pub handle_calls: Vec<(BeamPattern, BeamExpr)>,
908    /// Asynchronous cast handlers: (message_pattern, new_state_expr)
909    pub handle_casts: Vec<(BeamPattern, BeamExpr)>,
910    /// Info message handlers: (message_pattern, new_state_expr)
911    pub handle_infos: Vec<(BeamPattern, BeamExpr)>,
912    /// Initial state expression
913    pub init_state: BeamExpr,
914    /// Termination callback body
915    pub terminate_body: Option<BeamExpr>,
916}
917impl GenServerSpec {
918    /// Create a new minimal gen_server spec.
919    #[allow(dead_code)]
920    pub fn new(module_name: impl Into<String>, init_state: BeamExpr) -> Self {
921        GenServerSpec {
922            module_name: module_name.into(),
923            state_doc: "State".to_string(),
924            handle_calls: Vec::new(),
925            handle_casts: Vec::new(),
926            handle_infos: Vec::new(),
927            init_state,
928            terminate_body: None,
929        }
930    }
931    /// Generate a complete BeamModule from this spec.
932    #[allow(dead_code)]
933    pub fn generate_module(&self) -> BeamModule {
934        let mut module = BeamModule::new(self.module_name.clone());
935        module.add_attribute("behaviour", "gen_server");
936        let mut start_link = BeamFunction::new("start_link", 0);
937        start_link.export();
938        module.add_function(start_link);
939        let mut init_fn = BeamFunction::new("init", 1);
940        init_fn.export();
941        module.add_function(init_fn);
942        let mut handle_call = BeamFunction::new("handle_call", 3);
943        handle_call.export();
944        module.add_function(handle_call);
945        let mut handle_cast = BeamFunction::new("handle_cast", 2);
946        handle_cast.export();
947        module.add_function(handle_cast);
948        let mut handle_info = BeamFunction::new("handle_info", 2);
949        handle_info.export();
950        module.add_function(handle_info);
951        let mut terminate = BeamFunction::new("terminate", 2);
952        terminate.export();
953        module.add_function(terminate);
954        module
955    }
956    /// Emit Core Erlang for the init/1 callback.
957    #[allow(dead_code)]
958    pub fn emit_init(&self) -> String {
959        format!("init([]) ->\n    {{ok, {}}}.\n", "State")
960    }
961}
962/// Links multiple BEAM modules into a combined module by merging functions.
963///
964/// Used for compiling multiple source files into a single Erlang module
965/// (e.g., when inlining helper libraries).
966#[allow(dead_code)]
967#[derive(Debug, Clone, Default)]
968pub struct BeamLinker {
969    /// Modules being linked
970    pub(super) modules: Vec<BeamModule>,
971    /// Rename map: (original_module, original_name) → new_name
972    pub(super) rename_map: HashMap<(String, String), String>,
973    /// Target module name
974    pub(super) target_name: String,
975}
976impl BeamLinker {
977    /// Create a new linker targeting the given module name.
978    #[allow(dead_code)]
979    pub fn new(target: impl Into<String>) -> Self {
980        BeamLinker {
981            modules: Vec::new(),
982            rename_map: HashMap::new(),
983            target_name: target.into(),
984        }
985    }
986    /// Add a module to be linked.
987    #[allow(dead_code)]
988    pub fn add_module(&mut self, module: BeamModule) {
989        self.modules.push(module);
990    }
991    /// Rename a function during linking to avoid name collisions.
992    #[allow(dead_code)]
993    pub fn rename(
994        &mut self,
995        src_module: impl Into<String>,
996        src_name: impl Into<String>,
997        new_name: impl Into<String>,
998    ) {
999        self.rename_map
1000            .insert((src_module.into(), src_name.into()), new_name.into());
1001    }
1002    /// Perform the link and produce a merged BeamModule.
1003    #[allow(dead_code)]
1004    pub fn link(self) -> BeamModule {
1005        let mut result = BeamModule::new(self.target_name);
1006        for module in self.modules {
1007            for mut func in module.functions {
1008                let key = (module.name.clone(), func.name.clone());
1009                if let Some(new_name) = self.rename_map.get(&key) {
1010                    func.name = new_name.clone();
1011                }
1012                result.add_function(func);
1013            }
1014            for attr in module.attributes {
1015                result.attributes.push(attr);
1016            }
1017        }
1018        result
1019    }
1020}
1021/// A constant pool for BEAM modules (literal table).
1022///
1023/// BEAM stores large constants (tuples, lists, binaries) in a literal pool
1024/// to avoid re-creating them on every call.
1025#[allow(dead_code)]
1026#[derive(Debug, Clone, Default)]
1027pub struct BeamConstPool {
1028    /// Literals stored in the pool
1029    pub(super) literals: Vec<BeamExpr>,
1030    /// Map from a canonical string key to index
1031    pub(super) index_map: HashMap<String, u32>,
1032}
1033impl BeamConstPool {
1034    /// Create an empty constant pool.
1035    #[allow(dead_code)]
1036    pub fn new() -> Self {
1037        Self::default()
1038    }
1039    /// Intern a literal into the pool, returning its index.
1040    #[allow(dead_code)]
1041    pub fn intern(&mut self, expr: BeamExpr, key: impl Into<String>) -> u32 {
1042        let k = key.into();
1043        if let Some(&idx) = self.index_map.get(&k) {
1044            return idx;
1045        }
1046        let idx = self.literals.len() as u32;
1047        self.literals.push(expr);
1048        self.index_map.insert(k, idx);
1049        idx
1050    }
1051    /// Get a literal by index.
1052    #[allow(dead_code)]
1053    pub fn get(&self, idx: u32) -> Option<&BeamExpr> {
1054        self.literals.get(idx as usize)
1055    }
1056    /// Number of literals in the pool.
1057    #[allow(dead_code)]
1058    pub fn len(&self) -> usize {
1059        self.literals.len()
1060    }
1061    /// Whether the pool is empty.
1062    #[allow(dead_code)]
1063    pub fn is_empty(&self) -> bool {
1064        self.literals.is_empty()
1065    }
1066}
1067/// Simple register allocator for BEAM X-registers.
1068///
1069/// BEAM X registers are general-purpose argument/result registers.
1070/// This allocator assigns virtual variables to X registers.
1071#[allow(dead_code)]
1072#[derive(Debug, Clone, Default)]
1073pub struct XRegAllocator {
1074    /// Next free X register index
1075    pub(super) next_x: u32,
1076    /// Map from variable name to assigned X register
1077    pub(super) assignment: HashMap<String, u32>,
1078    /// Maximum X register used
1079    pub(super) max_x: u32,
1080}
1081impl XRegAllocator {
1082    /// Create a new allocator.
1083    #[allow(dead_code)]
1084    pub fn new() -> Self {
1085        Self::default()
1086    }
1087    /// Allocate a fresh X register for a variable.
1088    #[allow(dead_code)]
1089    pub fn alloc(&mut self, var: impl Into<String>) -> u32 {
1090        let x = self.next_x;
1091        let name = var.into();
1092        self.assignment.insert(name, x);
1093        if x > self.max_x {
1094            self.max_x = x;
1095        }
1096        self.next_x += 1;
1097        x
1098    }
1099    /// Get the X register for an already-allocated variable.
1100    #[allow(dead_code)]
1101    pub fn get(&self, var: &str) -> Option<u32> {
1102        self.assignment.get(var).copied()
1103    }
1104    /// Free all allocations and reset to start.
1105    #[allow(dead_code)]
1106    pub fn reset(&mut self) {
1107        self.next_x = 0;
1108        self.assignment.clear();
1109        self.max_x = 0;
1110    }
1111    /// Number of X registers used.
1112    #[allow(dead_code)]
1113    pub fn registers_used(&self) -> u32 {
1114        if self.assignment.is_empty() {
1115            0
1116        } else {
1117            self.max_x + 1
1118        }
1119    }
1120}
1121/// BEAM VM code generation backend.
1122///
1123/// Converts LCNF IR into Core Erlang source representation suitable for
1124/// further compilation by the Erlang compiler (`erlc`).
1125pub struct BeamBackend {
1126    /// The module being generated
1127    pub module: BeamModule,
1128    /// Emit context
1129    pub(super) ctx: BeamEmitCtx,
1130    /// Mapping from LCNF variable IDs to BEAM variable names
1131    pub(super) var_map: HashMap<u64, String>,
1132    /// Label counter for instruction lowering
1133    pub(super) next_label: u32,
1134}
1135impl BeamBackend {
1136    /// Create a new BEAM backend for the given module name.
1137    pub fn new(module_name: impl Into<String>) -> Self {
1138        BeamBackend {
1139            module: BeamModule::new(module_name),
1140            ctx: BeamEmitCtx::new(),
1141            var_map: HashMap::new(),
1142            next_label: 1,
1143        }
1144    }
1145    /// Allocate a fresh label.
1146    pub(super) fn fresh_label(&mut self) -> u32 {
1147        let l = self.next_label;
1148        self.next_label += 1;
1149        l
1150    }
1151    /// Map an LCNF variable to a BEAM variable name.
1152    pub(super) fn beam_var(&mut self, id: LcnfVarId) -> String {
1153        self.var_map
1154            .entry(id.0)
1155            .or_insert_with(|| format!("_X{}", id.0))
1156            .clone()
1157    }
1158    /// Convert an LCNF literal to a BeamExpr.
1159    pub fn emit_literal(&self, lit: &LcnfLit) -> BeamExpr {
1160        match lit {
1161            LcnfLit::Nat(n) => BeamExpr::LitInt(*n as i64),
1162            LcnfLit::Str(s) => BeamExpr::LitString(s.clone()),
1163        }
1164    }
1165    /// Convert an LCNF argument to a BeamExpr.
1166    pub fn emit_arg(&mut self, arg: &LcnfArg) -> BeamExpr {
1167        match arg {
1168            LcnfArg::Var(id) => BeamExpr::Var(self.beam_var(*id)),
1169            LcnfArg::Lit(lit) => self.emit_literal(lit),
1170            LcnfArg::Erased => BeamExpr::LitAtom("erased".to_string()),
1171            LcnfArg::Type(_) => BeamExpr::LitAtom("type".to_string()),
1172        }
1173    }
1174    /// Convert an LCNF let-value to a BeamExpr.
1175    #[allow(clippy::too_many_arguments)]
1176    pub fn emit_let_value(&mut self, val: &LcnfLetValue) -> BeamExpr {
1177        match val {
1178            LcnfLetValue::App(func, args) => {
1179                let func_expr = self.emit_arg(func);
1180                let arg_exprs: Vec<BeamExpr> = args.iter().map(|a| self.emit_arg(a)).collect();
1181                BeamExpr::Apply {
1182                    fun: Box::new(func_expr),
1183                    args: arg_exprs,
1184                }
1185            }
1186            LcnfLetValue::Proj(struct_name, idx, var) => {
1187                let var_expr = BeamExpr::Var(self.beam_var(*var));
1188                let beam_idx = (*idx as i64) + 2;
1189                BeamExpr::Primop {
1190                    name: format!("element({}, {})", beam_idx, struct_name),
1191                    args: vec![var_expr],
1192                }
1193            }
1194            LcnfLetValue::Ctor(name, _tag, args) => {
1195                let mut elems = vec![BeamExpr::LitAtom(sanitize_atom(name))];
1196                for a in args {
1197                    elems.push(self.emit_arg(a));
1198                }
1199                BeamExpr::Tuple(elems)
1200            }
1201            LcnfLetValue::Lit(lit) => self.emit_literal(lit),
1202            LcnfLetValue::Erased => BeamExpr::LitAtom("erased".to_string()),
1203            LcnfLetValue::FVar(id) => BeamExpr::Var(self.beam_var(*id)),
1204            LcnfLetValue::Reset(var) => BeamExpr::Primop {
1205                name: "reset".to_string(),
1206                args: vec![BeamExpr::Var(self.beam_var(*var))],
1207            },
1208            LcnfLetValue::Reuse(slot, name, _tag, args) => {
1209                let slot_expr = BeamExpr::Var(self.beam_var(*slot));
1210                let mut elems = vec![BeamExpr::LitAtom(sanitize_atom(name)), slot_expr];
1211                for a in args {
1212                    elems.push(self.emit_arg(a));
1213                }
1214                BeamExpr::Tuple(elems)
1215            }
1216        }
1217    }
1218    /// Emit a Core Erlang expression from an LCNF expression.
1219    #[allow(clippy::too_many_arguments)]
1220    pub fn emit_expr(&mut self, expr: &LcnfExpr) -> BeamExpr {
1221        match expr {
1222            LcnfExpr::Let {
1223                id, value, body, ..
1224            } => {
1225                let vname = self.beam_var(*id);
1226                let val_expr = self.emit_let_value(value);
1227                let body_expr = self.emit_expr(body);
1228                BeamExpr::Let {
1229                    var: vname,
1230                    value: Box::new(val_expr),
1231                    body: Box::new(body_expr),
1232                }
1233            }
1234            LcnfExpr::Case {
1235                scrutinee,
1236                alts,
1237                default,
1238                ..
1239            } => {
1240                let subj_expr = BeamExpr::Var(self.beam_var(*scrutinee));
1241                let mut clauses: Vec<BeamClause> =
1242                    alts.iter().map(|alt| self.emit_case_alt(alt)).collect();
1243                if let Some(def_body) = default {
1244                    let def_expr = self.emit_expr(def_body);
1245                    clauses.push(BeamClause {
1246                        pattern: BeamPattern::Wildcard,
1247                        guard: None,
1248                        body: def_expr,
1249                    });
1250                }
1251                BeamExpr::Case {
1252                    subject: Box::new(subj_expr),
1253                    clauses,
1254                }
1255            }
1256            LcnfExpr::Return(arg) => self.emit_arg(arg),
1257            LcnfExpr::Unreachable => BeamExpr::Primop {
1258                name: "error".to_string(),
1259                args: vec![BeamExpr::Tuple(vec![
1260                    BeamExpr::LitAtom("error".to_string()),
1261                    BeamExpr::LitAtom("unreachable".to_string()),
1262                ])],
1263            },
1264            LcnfExpr::TailCall(func, args) => {
1265                let func_expr = self.emit_arg(func);
1266                let arg_exprs: Vec<BeamExpr> = args.iter().map(|a| self.emit_arg(a)).collect();
1267                BeamExpr::Apply {
1268                    fun: Box::new(func_expr),
1269                    args: arg_exprs,
1270                }
1271            }
1272        }
1273    }
1274    /// Emit a BeamClause from an LCNF case alternative.
1275    pub(super) fn emit_case_alt(&mut self, alt: &LcnfAlt) -> BeamClause {
1276        let mut pats = vec![BeamPattern::LitAtom(sanitize_atom(&alt.ctor_name))];
1277        for param in &alt.params {
1278            pats.push(BeamPattern::Var(self.beam_var(param.id)));
1279        }
1280        let body = self.emit_expr(&alt.body);
1281        BeamClause {
1282            pattern: BeamPattern::Tuple(pats),
1283            guard: None,
1284            body,
1285        }
1286    }
1287    /// Emit a complete LCNF function declaration as a BeamFunction.
1288    pub fn emit_fun_decl(&mut self, decl: &LcnfFunDecl) -> BeamFunction {
1289        let mut func = BeamFunction::new(sanitize_atom(&decl.name), decl.params.len());
1290        func.params = decl.params.iter().map(|p| self.beam_var(p.id)).collect();
1291        let patterns = func
1292            .params
1293            .iter()
1294            .map(|p| BeamPattern::Var(p.clone()))
1295            .collect();
1296        let body = self.emit_expr(&decl.body);
1297        func.add_clause(BeamClause {
1298            pattern: BeamPattern::Tuple(patterns),
1299            guard: None,
1300            body,
1301        });
1302        self.lower_function(&mut func);
1303        func
1304    }
1305    /// Lower a BeamFunction's clauses to a linear instruction sequence.
1306    pub(super) fn lower_function(&mut self, func: &mut BeamFunction) {
1307        let entry = self.fresh_label();
1308        func.instrs.push(BeamInstr::Label(entry));
1309        func.instrs.push(BeamInstr::FuncInfo {
1310            module: self.module.name.clone(),
1311            function: func.name.clone(),
1312            arity: func.arity as u32,
1313        });
1314        let body_label = self.fresh_label();
1315        func.instrs.push(BeamInstr::Label(body_label));
1316        for (i, _param) in func.params.iter().enumerate() {
1317            func.instrs.push(BeamInstr::Move {
1318                src: BeamReg::X(i as u32),
1319                dst: BeamReg::Y(i as u32),
1320            });
1321        }
1322        func.instrs.push(BeamInstr::Return);
1323    }
1324    /// Emit a full Core Erlang source file for the module.
1325    pub fn emit_core_erlang(&mut self) -> String {
1326        let mut out = String::new();
1327        out.push_str(&format!("module '{}'\n", self.module.name));
1328        out.push_str("  [");
1329        let exports = self.module.export_list();
1330        for (i, exp) in exports.iter().enumerate() {
1331            if i > 0 {
1332                out.push_str(", ");
1333            }
1334            out.push_str(&format!("'{}'", exp));
1335        }
1336        out.push_str("]\n");
1337        out.push_str("  attributes []\n");
1338        for func in &self.module.functions.clone() {
1339            out.push_str(&self.emit_function_core_erlang(func));
1340        }
1341        out.push_str("end\n");
1342        out
1343    }
1344    /// Emit a single function in Core Erlang syntax.
1345    pub(super) fn emit_function_core_erlang(&self, func: &BeamFunction) -> String {
1346        let mut out = String::new();
1347        let params_str = func
1348            .params
1349            .iter()
1350            .map(|p| format!("_{}", p))
1351            .collect::<Vec<_>>()
1352            .join(", ");
1353        out.push_str(&format!(
1354            "\n'{}'/{}  =\n  fun ({}) ->\n",
1355            func.name, func.arity, params_str
1356        ));
1357        out.push_str("    'ok'\n");
1358        out
1359    }
1360    /// Emit BEAM assembly (human-readable instruction dump).
1361    pub fn emit_asm(&self) -> String {
1362        let mut out = String::new();
1363        out.push_str(&format!("{{module, '{}'}}.\n\n", self.module.name));
1364        out.push_str(&format!(
1365            "{{exports, [{}]}}.\n\n",
1366            self.module
1367                .exports
1368                .iter()
1369                .map(|(n, a)| format!("{{{}, {}}}", n, a))
1370                .collect::<Vec<_>>()
1371                .join(", ")
1372        ));
1373        for func in &self.module.functions {
1374            out.push_str(&format!("%% Function: {}/{}\n", func.name, func.arity));
1375            for instr in &func.instrs {
1376                out.push_str(&format!("  {}\n", emit_instr(instr)));
1377            }
1378            out.push('\n');
1379        }
1380        out
1381    }
1382}
1383/// A pretty-printer for BEAM expressions producing human-readable Core Erlang.
1384#[allow(dead_code)]
1385pub struct BeamPrinter {
1386    pub(super) indent: usize,
1387    pub(super) buf: String,
1388}
1389impl BeamPrinter {
1390    /// Create a new printer.
1391    #[allow(dead_code)]
1392    pub fn new() -> Self {
1393        BeamPrinter {
1394            indent: 0,
1395            buf: String::new(),
1396        }
1397    }
1398    pub(super) fn pad(&self) -> String {
1399        "  ".repeat(self.indent)
1400    }
1401    pub(super) fn push(&mut self, s: &str) {
1402        self.buf.push_str(s);
1403    }
1404    pub(super) fn newline(&mut self) {
1405        self.buf.push('\n');
1406        self.buf.push_str(&self.pad());
1407    }
1408    /// Print a BeamExpr.
1409    #[allow(dead_code)]
1410    pub fn print_expr(&mut self, expr: &BeamExpr) {
1411        match expr {
1412            BeamExpr::LitInt(n) => self.push(&n.to_string()),
1413            BeamExpr::LitFloat(v) => self.push(&format!("{:.6}", v)),
1414            BeamExpr::LitAtom(a) => self.push(&format!("'{}'", a)),
1415            BeamExpr::LitString(s) => self.push(&format!("\"{}\"", s)),
1416            BeamExpr::Nil => self.push("[]"),
1417            BeamExpr::Var(name) => self.push(name),
1418            BeamExpr::Tuple(elems) => {
1419                self.push("{");
1420                for (i, e) in elems.iter().enumerate() {
1421                    if i > 0 {
1422                        self.push(", ");
1423                    }
1424                    self.print_expr(e);
1425                }
1426                self.push("}");
1427            }
1428            BeamExpr::Cons(head, tail) => {
1429                self.push("[");
1430                self.print_expr(head);
1431                self.push(" | ");
1432                self.print_expr(tail);
1433                self.push("]");
1434            }
1435            BeamExpr::Let { var, value, body } => {
1436                self.push(&format!("let {} =", var));
1437                self.indent += 1;
1438                self.newline();
1439                self.print_expr(value);
1440                self.indent -= 1;
1441                self.newline();
1442                self.push("in ");
1443                self.print_expr(body);
1444            }
1445            BeamExpr::Apply { fun, args } => {
1446                self.push("apply ");
1447                self.print_expr(fun);
1448                self.push("(");
1449                for (i, a) in args.iter().enumerate() {
1450                    if i > 0 {
1451                        self.push(", ");
1452                    }
1453                    self.print_expr(a);
1454                }
1455                self.push(")");
1456            }
1457            BeamExpr::Call { module, func, args } => {
1458                if let Some(m) = module {
1459                    self.push(&format!("call '{}':'{}' (", m, func));
1460                } else {
1461                    self.push(&format!("call '{}' (", func));
1462                }
1463                for (i, a) in args.iter().enumerate() {
1464                    if i > 0 {
1465                        self.push(", ");
1466                    }
1467                    self.print_expr(a);
1468                }
1469                self.push(")");
1470            }
1471            BeamExpr::Primop { name, args } => {
1472                self.push(&format!("primop '{}' (", name));
1473                for (i, a) in args.iter().enumerate() {
1474                    if i > 0 {
1475                        self.push(", ");
1476                    }
1477                    self.print_expr(a);
1478                }
1479                self.push(")");
1480            }
1481            _ => self.push("..."),
1482        }
1483    }
1484    /// Return the accumulated output.
1485    #[allow(dead_code)]
1486    pub fn finish(self) -> String {
1487        self.buf
1488    }
1489}
1490/// A BEAM module containing functions and metadata.
1491#[derive(Debug, Clone)]
1492pub struct BeamModule {
1493    /// Module name (atom)
1494    pub name: String,
1495    /// Module-level attributes (e.g., `{vsn, [12345]}`)
1496    pub attributes: Vec<(String, String)>,
1497    /// All functions defined in the module
1498    pub functions: Vec<BeamFunction>,
1499    /// Exported functions as (Name, Arity) pairs
1500    pub exports: Vec<(String, usize)>,
1501    /// On-load function (optional)
1502    pub on_load: Option<String>,
1503    /// Module info strings (for beam_lib)
1504    pub compile_info: HashMap<String, String>,
1505}
1506impl BeamModule {
1507    /// Create a new empty module.
1508    pub fn new(name: impl Into<String>) -> Self {
1509        BeamModule {
1510            name: name.into(),
1511            attributes: Vec::new(),
1512            functions: Vec::new(),
1513            exports: Vec::new(),
1514            on_load: None,
1515            compile_info: HashMap::new(),
1516        }
1517    }
1518    /// Add a function to the module.
1519    pub fn add_function(&mut self, func: BeamFunction) {
1520        if func.exported {
1521            self.exports.push((func.name.clone(), func.arity));
1522        }
1523        self.functions.push(func);
1524    }
1525    /// Add a module attribute.
1526    pub fn add_attribute(&mut self, key: impl Into<String>, value: impl Into<String>) {
1527        self.attributes.push((key.into(), value.into()));
1528    }
1529    /// Find a function by name and arity.
1530    pub fn find_function(&self, name: &str, arity: usize) -> Option<&BeamFunction> {
1531        self.functions
1532            .iter()
1533            .find(|f| f.name == name && f.arity == arity)
1534    }
1535    /// Returns the list of exported function keys.
1536    pub fn export_list(&self) -> Vec<String> {
1537        self.exports
1538            .iter()
1539            .map(|(n, a)| format!("{}/{}", n, a))
1540            .collect()
1541    }
1542}
1543/// A BEAM register operand.
1544#[derive(Debug, Clone, PartialEq)]
1545pub enum BeamReg {
1546    /// X register (general-purpose argument/return register)
1547    X(u32),
1548    /// Y register (stack slot / saved value)
1549    Y(u32),
1550    /// Floating-point register
1551    FR(u32),
1552}