Skip to main content

oxilean_codegen/beam_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6
7use super::types::{
8    AttributeBuilder, BeamBackend, BeamConstPool, BeamDeadEliminator, BeamEndian, BeamExpr,
9    BeamFunction, BeamInstr, BeamLinker, BeamModule, BeamPattern, BeamPrinter, BeamProcess,
10    BeamReg, BeamType, BeamTypeCtx, EtsAccess, EtsTable, EtsType, GenServerSpec, PatternNormalizer,
11    TailCallInfo, XRegAllocator,
12};
13
14/// Format a single BEAM instruction as a string.
15pub fn emit_instr(instr: &BeamInstr) -> String {
16    match instr {
17        BeamInstr::Label(l) => format!("label {}.", l),
18        BeamInstr::FuncInfo {
19            module,
20            function,
21            arity,
22        } => {
23            format!("func_info '{}' '{}' {}.", module, function, arity)
24        }
25        BeamInstr::Call { arity, label } => format!("call {} {}.", arity, label),
26        BeamInstr::CallLast {
27            arity,
28            label,
29            deallocate,
30        } => {
31            format!("call_last {} {} {}.", arity, label, deallocate)
32        }
33        BeamInstr::CallExt { arity, destination } => {
34            format!(
35                "call_ext {} {{extfunc, '{}', '{}', {}}}.",
36                arity, destination.module, destination.function, destination.arity
37            )
38        }
39        BeamInstr::CallExtLast {
40            arity,
41            destination,
42            deallocate,
43        } => {
44            format!(
45                "call_ext_last {} {{extfunc, '{}', '{}', {}}} {}.",
46                arity, destination.module, destination.function, destination.arity, deallocate
47            )
48        }
49        BeamInstr::CallFun { arity } => format!("call_fun {}.", arity),
50        BeamInstr::Move { src, dst } => format!("move {} {}.", src, dst),
51        BeamInstr::PutTuple { arity, dst } => format!("put_tuple {} {}.", arity, dst),
52        BeamInstr::Put(val) => format!("put {}.", val),
53        BeamInstr::GetTupleElement { src, index, dst } => {
54            format!("get_tuple_element {} {} {}.", src, index, dst)
55        }
56        BeamInstr::SetTupleElement {
57            value,
58            tuple,
59            index,
60        } => {
61            format!("set_tuple_element {} {} {}.", value, tuple, index)
62        }
63        BeamInstr::IsEq { fail, lhs, rhs } => format!("is_eq {} {} {}.", fail, lhs, rhs),
64        BeamInstr::IsEqExact { fail, lhs, rhs } => {
65            format!("is_eq_exact {} {} {}.", fail, lhs, rhs)
66        }
67        BeamInstr::IsNe { fail, lhs, rhs } => format!("is_ne {} {} {}.", fail, lhs, rhs),
68        BeamInstr::IsLt { fail, lhs, rhs } => format!("is_lt {} {} {}.", fail, lhs, rhs),
69        BeamInstr::IsGe { fail, lhs, rhs } => format!("is_ge {} {} {}.", fail, lhs, rhs),
70        BeamInstr::IsInteger { fail, arg } => format!("is_integer {} {}.", fail, arg),
71        BeamInstr::IsFloat { fail, arg } => format!("is_float {} {}.", fail, arg),
72        BeamInstr::IsAtom { fail, arg } => format!("is_atom {} {}.", fail, arg),
73        BeamInstr::IsNil { fail, arg } => format!("is_nil {} {}.", fail, arg),
74        BeamInstr::IsList { fail, arg } => format!("is_list {} {}.", fail, arg),
75        BeamInstr::IsTuple { fail, arg } => format!("is_tuple {} {}.", fail, arg),
76        BeamInstr::IsBinary { fail, arg } => format!("is_binary {} {}.", fail, arg),
77        BeamInstr::IsFunction { fail, arg } => format!("is_function {} {}.", fail, arg),
78        BeamInstr::Jump(l) => format!("jump {}.", l),
79        BeamInstr::Return => "return.".to_string(),
80        BeamInstr::Send => "send.".to_string(),
81        BeamInstr::RemoveMessage => "remove_message.".to_string(),
82        BeamInstr::LoopRec { fail, dst } => format!("loop_rec {} {}.", fail, dst),
83        BeamInstr::Wait(l) => format!("wait {}.", l),
84        BeamInstr::WaitTimeout { fail, timeout } => {
85            format!("wait_timeout {} {}.", fail, timeout)
86        }
87        BeamInstr::GcBif {
88            name,
89            fail,
90            live,
91            args,
92            dst,
93        } => {
94            let args_str = args
95                .iter()
96                .map(|a| a.to_string())
97                .collect::<Vec<_>>()
98                .join(", ");
99            format!(
100                "gc_bif '{}' {} {} [{}] {}.",
101                name, fail, live, args_str, dst
102            )
103        }
104        BeamInstr::Bif0 { name, dst } => format!("bif '{}' {}.", name, dst),
105        BeamInstr::Allocate { stack_need, live } => {
106            format!("allocate {} {}.", stack_need, live)
107        }
108        BeamInstr::Deallocate(n) => format!("deallocate {}.", n),
109        BeamInstr::Init(r) => format!("init {}.", r),
110        BeamInstr::MakeFun2(idx) => format!("make_fun2 {}.", idx),
111        BeamInstr::GetList { src, head, tail } => {
112            format!("get_list {} {} {}.", src, head, tail)
113        }
114        BeamInstr::PutList { head, tail, dst } => {
115            format!("put_list {} {} {}.", head, tail, dst)
116        }
117        BeamInstr::Raise { class, reason } => format!("raise {} {}.", class, reason),
118        BeamInstr::TryBegin { label, reg } => format!("try {} {}.", label, reg),
119        BeamInstr::TryEnd(r) => format!("try_end {}.", r),
120        BeamInstr::TryCase(r) => format!("try_case {}.", r),
121        BeamInstr::Comment(s) => format!("%% {}", s),
122    }
123}
124/// Sanitize a string to be a valid Erlang atom (lowercase, no special chars).
125pub fn sanitize_atom(s: &str) -> String {
126    let mut out = String::new();
127    for (i, ch) in s.chars().enumerate() {
128        if ch.is_alphanumeric() || ch == '_' {
129            if i == 0 && ch.is_uppercase() {
130                for c in ch.to_lowercase() {
131                    out.push(c);
132                }
133            } else {
134                out.push(ch);
135            }
136        } else {
137            out.push('_');
138        }
139    }
140    if out.is_empty() {
141        out.push_str("unnamed");
142    }
143    out
144}
145#[cfg(test)]
146mod tests {
147    use super::*;
148    pub(super) fn make_backend() -> BeamBackend {
149        BeamBackend::new("test_module")
150    }
151    #[test]
152    pub(super) fn test_beam_type_display_primitives() {
153        assert_eq!(BeamType::Integer.to_string(), "integer()");
154        assert_eq!(BeamType::Float.to_string(), "float()");
155        assert_eq!(BeamType::Atom.to_string(), "atom()");
156        assert_eq!(BeamType::Pid.to_string(), "pid()");
157        assert_eq!(BeamType::Any.to_string(), "any()");
158        assert_eq!(BeamType::None.to_string(), "none()");
159    }
160    #[test]
161    pub(super) fn test_beam_type_display_compound() {
162        let list_ty = BeamType::List(Box::new(BeamType::Integer));
163        assert_eq!(list_ty.to_string(), "list(integer())");
164        let tuple_ty = BeamType::Tuple(vec![BeamType::Atom, BeamType::Integer]);
165        assert_eq!(tuple_ty.to_string(), "{atom(), integer()}");
166        let map_ty = BeamType::Map(Box::new(BeamType::Atom), Box::new(BeamType::Any));
167        assert_eq!(map_ty.to_string(), "#{atom()  => any()}");
168    }
169    #[test]
170    pub(super) fn test_beam_type_fun_display() {
171        let fun_ty = BeamType::Fun(
172            vec![BeamType::Integer, BeamType::Integer],
173            Box::new(BeamType::Integer),
174        );
175        assert_eq!(
176            fun_ty.to_string(),
177            "fun((integer(), integer()) -> integer())"
178        );
179    }
180    #[test]
181    pub(super) fn test_beam_module_creation() {
182        let mut m = BeamModule::new("my_app");
183        m.add_attribute("author", "oxilean");
184        assert_eq!(m.name, "my_app");
185        assert_eq!(m.attributes.len(), 1);
186        assert_eq!(m.exports.len(), 0);
187    }
188    #[test]
189    pub(super) fn test_beam_module_add_exported_function() {
190        let mut m = BeamModule::new("my_mod");
191        let mut f = BeamFunction::new("add", 2);
192        f.export();
193        m.add_function(f);
194        assert_eq!(m.exports.len(), 1);
195        assert_eq!(m.export_list(), vec!["add/2"]);
196    }
197    #[test]
198    pub(super) fn test_emit_literal_nat() {
199        let backend = make_backend();
200        let lit = LcnfLit::Nat(42);
201        match backend.emit_literal(&lit) {
202            BeamExpr::LitInt(n) => assert_eq!(n, 42),
203            other => panic!("Expected LitInt, got {:?}", other),
204        }
205    }
206    #[test]
207    pub(super) fn test_emit_literal_str() {
208        let backend = make_backend();
209        match backend.emit_literal(&LcnfLit::Str("hello".to_string())) {
210            BeamExpr::LitString(s) => assert_eq!(s, "hello"),
211            other => panic!("Expected LitString, got {:?}", other),
212        }
213    }
214    #[test]
215    pub(super) fn test_sanitize_atom() {
216        assert_eq!(sanitize_atom("hello"), "hello");
217        assert_eq!(sanitize_atom("Hello"), "hello");
218        assert_eq!(sanitize_atom("my.func"), "my_func");
219        assert_eq!(sanitize_atom(""), "unnamed");
220        assert_eq!(sanitize_atom("add_two"), "add_two");
221    }
222    #[test]
223    pub(super) fn test_emit_asm_contains_module() {
224        let mut backend = make_backend();
225        let mut f = BeamFunction::new("main", 0);
226        f.export();
227        backend.module.add_function(f);
228        let asm = backend.emit_asm();
229        assert!(asm.contains("test_module"));
230        assert!(asm.contains("main"));
231    }
232    #[test]
233    pub(super) fn test_emit_instr_move() {
234        let instr = BeamInstr::Move {
235            src: BeamReg::X(0),
236            dst: BeamReg::Y(0),
237        };
238        assert_eq!(emit_instr(&instr), "move x(0) y(0).");
239    }
240    #[test]
241    pub(super) fn test_emit_instr_call() {
242        let instr = BeamInstr::Call { arity: 2, label: 5 };
243        assert_eq!(emit_instr(&instr), "call 2 5.");
244    }
245    #[test]
246    pub(super) fn test_beam_function_key() {
247        let f = BeamFunction::new("factorial", 1);
248        assert_eq!(f.key(), "factorial/1");
249    }
250    #[test]
251    pub(super) fn test_emit_var_arg() {
252        let mut backend = make_backend();
253        let arg = LcnfArg::Var(LcnfVarId(7));
254        match backend.emit_arg(&arg) {
255            BeamExpr::Var(name) => assert!(name.contains('7')),
256            other => panic!("Expected Var, got {:?}", other),
257        }
258    }
259}
260/// Check structural equality of two normalized patterns.
261#[allow(dead_code)]
262pub fn patterns_structurally_equal(a: &BeamPattern, b: &BeamPattern) -> bool {
263    match (a, b) {
264        (BeamPattern::Wildcard, BeamPattern::Wildcard) => true,
265        (BeamPattern::Var(_), BeamPattern::Var(_)) => true,
266        (BeamPattern::Nil, BeamPattern::Nil) => true,
267        (BeamPattern::LitInt(x), BeamPattern::LitInt(y)) => x == y,
268        (BeamPattern::LitAtom(x), BeamPattern::LitAtom(y)) => x == y,
269        (BeamPattern::Cons(ah, at), BeamPattern::Cons(bh, bt)) => {
270            patterns_structurally_equal(ah, bh) && patterns_structurally_equal(at, bt)
271        }
272        (BeamPattern::Tuple(ap), BeamPattern::Tuple(bp)) => {
273            ap.len() == bp.len()
274                && ap
275                    .iter()
276                    .zip(bp.iter())
277                    .all(|(x, y)| patterns_structurally_equal(x, y))
278        }
279        _ => false,
280    }
281}
282/// Analyse a BeamExpr for tail calls (returns info for `func_name`).
283#[allow(dead_code)]
284pub fn analyse_tail_calls(expr: &BeamExpr, func_name: &str) -> TailCallInfo {
285    let mut info = TailCallInfo::new();
286    collect_tail_calls_expr(expr, func_name, &mut info);
287    info
288}
289pub(super) fn collect_tail_calls_expr(expr: &BeamExpr, func_name: &str, info: &mut TailCallInfo) {
290    match expr {
291        BeamExpr::Apply { fun, .. } => {
292            if let BeamExpr::Var(name) = fun.as_ref() {
293                if name == func_name {
294                    info.add_self_tail();
295                } else {
296                    info.add_external_tail(name.clone());
297                }
298            }
299        }
300        BeamExpr::Call { func, .. } => {
301            if func == func_name {
302                info.add_self_tail();
303            } else {
304                info.add_external_tail(func.clone());
305            }
306        }
307        BeamExpr::Let { body, .. } => {
308            collect_tail_calls_expr(body, func_name, info);
309        }
310        BeamExpr::Case { clauses, .. } => {
311            for clause in clauses {
312                collect_tail_calls_expr(&clause.body, func_name, info);
313            }
314        }
315        BeamExpr::Seq(_, second) => {
316            collect_tail_calls_expr(second, func_name, info);
317        }
318        _ => {}
319    }
320}
321#[cfg(test)]
322mod extended_tests {
323    use super::*;
324    #[test]
325    pub(super) fn test_ets_table_type_display() {
326        assert_eq!(EtsType::Set.to_string(), "set");
327        assert_eq!(EtsType::OrderedSet.to_string(), "ordered_set");
328        assert_eq!(EtsType::Bag.to_string(), "bag");
329        assert_eq!(EtsType::DuplicateBag.to_string(), "duplicate_bag");
330    }
331    #[test]
332    pub(super) fn test_ets_access_display() {
333        assert_eq!(EtsAccess::Private.to_string(), "private");
334        assert_eq!(EtsAccess::Protected.to_string(), "protected");
335        assert_eq!(EtsAccess::Public.to_string(), "public");
336    }
337    #[test]
338    pub(super) fn test_ets_table_emit_new() {
339        let table = EtsTable::new_set("my_table");
340        let expr = table.emit_new();
341        match expr {
342            BeamExpr::Call { func, .. } => assert_eq!(func, "new"),
343            _ => panic!("expected Call"),
344        }
345    }
346    #[test]
347    pub(super) fn test_ets_table_emit_insert() {
348        let table = EtsTable::new_set("test");
349        let tuple = BeamExpr::Tuple(vec![BeamExpr::LitAtom("key".into()), BeamExpr::LitInt(42)]);
350        match table.emit_insert(tuple) {
351            BeamExpr::Call { func, .. } => assert_eq!(func, "insert"),
352            _ => panic!("expected Call"),
353        }
354    }
355    #[test]
356    pub(super) fn test_ets_table_emit_lookup() {
357        let table = EtsTable::new_set("test");
358        let key = BeamExpr::LitAtom("key".into());
359        match table.emit_lookup(key) {
360            BeamExpr::Call { func, .. } => assert_eq!(func, "lookup"),
361            _ => panic!("expected Call"),
362        }
363    }
364    #[test]
365    pub(super) fn test_ets_table_emit_delete() {
366        let table = EtsTable::new_set("test");
367        let key = BeamExpr::LitAtom("key".into());
368        match table.emit_delete(key) {
369            BeamExpr::Call { func, .. } => assert_eq!(func, "delete"),
370            _ => panic!("expected Call"),
371        }
372    }
373    #[test]
374    pub(super) fn test_beam_process_spawn_expr() {
375        let proc = BeamProcess::new("p1", "my_mod", "start")
376            .with_arg(BeamExpr::LitInt(0))
377            .linked();
378        let spawn = proc.emit_spawn();
379        match spawn {
380            BeamExpr::Call { func, .. } => assert_eq!(func, "spawn_link"),
381            _ => panic!("expected Call"),
382        }
383    }
384    #[test]
385    pub(super) fn test_beam_process_spawn_no_link() {
386        let proc = BeamProcess::new("p2", "mod", "init");
387        match proc.emit_spawn() {
388            BeamExpr::Call { func, .. } => assert_eq!(func, "spawn"),
389            _ => panic!("expected Call"),
390        }
391    }
392    #[test]
393    pub(super) fn test_gen_server_generate_module() {
394        let spec = GenServerSpec::new("counter", BeamExpr::LitInt(0));
395        let module = spec.generate_module();
396        assert_eq!(module.name, "counter");
397        assert!(!module.functions.is_empty());
398        assert!(module.functions.iter().any(|f| f.name == "init"));
399        assert!(module.functions.iter().any(|f| f.name == "handle_call"));
400    }
401    #[test]
402    pub(super) fn test_gen_server_emit_init() {
403        let spec = GenServerSpec::new("myserver", BeamExpr::LitAtom("idle".into()));
404        let s = spec.emit_init();
405        assert!(s.contains("init"));
406        assert!(s.contains("ok"));
407    }
408    #[test]
409    pub(super) fn test_beam_type_ctx_bind_lookup() {
410        let mut ctx = BeamTypeCtx::new();
411        ctx.bind("x", BeamType::Integer);
412        assert_eq!(ctx.lookup("x"), Some(&BeamType::Integer));
413        assert_eq!(ctx.lookup("y"), None);
414    }
415    #[test]
416    pub(super) fn test_beam_type_ctx_infer_literals() {
417        let ctx = BeamTypeCtx::new();
418        assert_eq!(ctx.infer(&BeamExpr::LitInt(1)), BeamType::Integer);
419        assert_eq!(ctx.infer(&BeamExpr::LitFloat(1.0)), BeamType::Float);
420        assert_eq!(ctx.infer(&BeamExpr::LitAtom("ok".into())), BeamType::Atom);
421    }
422    #[test]
423    pub(super) fn test_beam_type_ctx_infer_var() {
424        let mut ctx = BeamTypeCtx::new();
425        ctx.bind("n", BeamType::Integer);
426        assert_eq!(ctx.infer(&BeamExpr::Var("n".into())), BeamType::Integer);
427        assert_eq!(ctx.infer(&BeamExpr::Var("unknown".into())), BeamType::Any);
428    }
429    #[test]
430    pub(super) fn test_beam_type_ctx_infer_tuple() {
431        let ctx = BeamTypeCtx::new();
432        let expr = BeamExpr::Tuple(vec![BeamExpr::LitAtom("ok".into()), BeamExpr::LitInt(1)]);
433        let ty = ctx.infer(&expr);
434        assert!(matches!(ty, BeamType::Tuple(_)));
435    }
436    #[test]
437    pub(super) fn test_beam_type_ctx_merge() {
438        let mut a = BeamTypeCtx::new();
439        a.bind("x", BeamType::Integer);
440        let mut b = BeamTypeCtx::new();
441        b.bind("x", BeamType::Float);
442        b.bind("y", BeamType::Atom);
443        let merged = a.merge(&b);
444        assert_eq!(merged.lookup("x"), Some(&BeamType::Any));
445        assert_eq!(merged.lookup("y"), Some(&BeamType::Atom));
446    }
447    #[test]
448    pub(super) fn test_pattern_normalizer_wildcard() {
449        let mut norm = PatternNormalizer::new();
450        let p = norm.normalize(BeamPattern::Var("_Whatever".into()));
451        assert!(matches!(p, BeamPattern::Var(_)));
452    }
453    #[test]
454    pub(super) fn test_patterns_structurally_equal() {
455        let a = BeamPattern::Tuple(vec![
456            BeamPattern::LitAtom("ok".into()),
457            BeamPattern::Var("x".into()),
458        ]);
459        let b = BeamPattern::Tuple(vec![
460            BeamPattern::LitAtom("ok".into()),
461            BeamPattern::Var("y".into()),
462        ]);
463        assert!(patterns_structurally_equal(&a, &b));
464    }
465    #[test]
466    pub(super) fn test_patterns_structurally_not_equal() {
467        let a = BeamPattern::LitInt(1);
468        let b = BeamPattern::LitInt(2);
469        assert!(!patterns_structurally_equal(&a, &b));
470    }
471    #[test]
472    pub(super) fn test_beam_linker_merge() {
473        let mut m1 = BeamModule::new("mod_a");
474        m1.add_function(BeamFunction::new("foo", 0));
475        let mut m2 = BeamModule::new("mod_b");
476        m2.add_function(BeamFunction::new("bar", 1));
477        let mut linker = BeamLinker::new("combined");
478        linker.add_module(m1);
479        linker.add_module(m2);
480        let merged = linker.link();
481        assert_eq!(merged.name, "combined");
482        assert_eq!(merged.functions.len(), 2);
483    }
484    #[test]
485    pub(super) fn test_beam_linker_rename() {
486        let mut m = BeamModule::new("mod");
487        m.add_function(BeamFunction::new("helper", 1));
488        let mut linker = BeamLinker::new("output");
489        linker.rename("mod", "helper", "mod_helper");
490        linker.add_module(m);
491        let merged = linker.link();
492        assert!(merged.functions.iter().any(|f| f.name == "mod_helper"));
493    }
494    #[test]
495    pub(super) fn test_beam_dead_eliminator_seed_and_eliminate() {
496        let mut module = BeamModule::new("test");
497        let mut exported = BeamFunction::new("public_fn", 0);
498        exported.export();
499        module.add_function(exported);
500        module.add_function(BeamFunction::new("private_fn", 0));
501        let mut elim = BeamDeadEliminator::new();
502        elim.seed_exports(&module);
503        let after = elim.eliminate(module.clone());
504        assert!(!after.functions.is_empty());
505    }
506    #[test]
507    pub(super) fn test_beam_const_pool_intern() {
508        let mut pool = BeamConstPool::new();
509        let idx1 = pool.intern(BeamExpr::LitInt(42), "forty_two");
510        let idx2 = pool.intern(BeamExpr::LitInt(99), "ninety_nine");
511        let idx1_again = pool.intern(BeamExpr::LitInt(0), "forty_two");
512        assert_eq!(idx1, 0);
513        assert_eq!(idx2, 1);
514        assert_eq!(idx1_again, idx1);
515        assert_eq!(pool.len(), 2);
516    }
517    #[test]
518    pub(super) fn test_beam_const_pool_get() {
519        let mut pool = BeamConstPool::new();
520        let idx = pool.intern(BeamExpr::LitAtom("hello".into()), "greeting");
521        let retrieved = pool.get(idx);
522        assert!(retrieved.is_some());
523        assert!(
524            matches!(retrieved.expect("value should be Some/Ok"), BeamExpr::LitAtom(s) if s == "hello")
525        );
526    }
527    #[test]
528    pub(super) fn test_attribute_builder() {
529        let attrs = AttributeBuilder::new()
530            .vsn("1.0.0")
531            .author("oxilean")
532            .compile("debug_info")
533            .build();
534        assert_eq!(attrs.len(), 3);
535        assert_eq!(attrs[0], ("vsn".into(), "1.0.0".into()));
536    }
537    #[test]
538    pub(super) fn test_attribute_builder_apply() {
539        let mut module = BeamModule::new("mymod");
540        AttributeBuilder::new().author("test").apply(&mut module);
541        assert_eq!(module.attributes.len(), 1);
542    }
543    #[test]
544    pub(super) fn test_tail_call_info_self() {
545        let mut info = TailCallInfo::new();
546        info.add_self_tail();
547        assert!(info.is_tail_recursive);
548        assert_eq!(info.self_tail_calls, 1);
549        assert!(info.has_tail_calls());
550    }
551    #[test]
552    pub(super) fn test_analyse_tail_calls_apply() {
553        let expr = BeamExpr::Apply {
554            fun: Box::new(BeamExpr::Var("fact".into())),
555            args: vec![BeamExpr::LitInt(10)],
556        };
557        let info = analyse_tail_calls(&expr, "fact");
558        assert_eq!(info.self_tail_calls, 1);
559    }
560    #[test]
561    pub(super) fn test_analyse_tail_calls_let() {
562        let expr = BeamExpr::Let {
563            var: "x".into(),
564            value: Box::new(BeamExpr::LitInt(1)),
565            body: Box::new(BeamExpr::Apply {
566                fun: Box::new(BeamExpr::Var("go".into())),
567                args: vec![],
568            }),
569        };
570        let info = analyse_tail_calls(&expr, "go");
571        assert!(info.is_tail_recursive);
572    }
573    #[test]
574    pub(super) fn test_beam_printer_lit_int() {
575        let mut p = BeamPrinter::new();
576        p.print_expr(&BeamExpr::LitInt(42));
577        assert_eq!(p.finish(), "42");
578    }
579    #[test]
580    pub(super) fn test_beam_printer_var() {
581        let mut p = BeamPrinter::new();
582        p.print_expr(&BeamExpr::Var("X".into()));
583        assert_eq!(p.finish(), "X");
584    }
585    #[test]
586    pub(super) fn test_beam_printer_tuple() {
587        let mut p = BeamPrinter::new();
588        p.print_expr(&BeamExpr::Tuple(vec![
589            BeamExpr::LitAtom("ok".into()),
590            BeamExpr::LitInt(0),
591        ]));
592        let out = p.finish();
593        assert!(out.contains("'ok'"));
594        assert!(out.contains('0'));
595    }
596    #[test]
597    pub(super) fn test_x_reg_allocator_alloc() {
598        let mut alloc = XRegAllocator::new();
599        let r0 = alloc.alloc("x");
600        let r1 = alloc.alloc("y");
601        assert_eq!(r0, 0);
602        assert_eq!(r1, 1);
603        assert_eq!(alloc.get("x"), Some(0));
604        assert_eq!(alloc.registers_used(), 2);
605    }
606    #[test]
607    pub(super) fn test_x_reg_allocator_reset() {
608        let mut alloc = XRegAllocator::new();
609        alloc.alloc("a");
610        alloc.alloc("b");
611        alloc.reset();
612        assert_eq!(alloc.registers_used(), 0);
613        assert_eq!(alloc.get("a"), None);
614    }
615    #[test]
616    pub(super) fn test_beam_type_union_display() {
617        let u = BeamType::Union(vec![BeamType::Atom, BeamType::Integer]);
618        let s = u.to_string();
619        assert!(s.contains("atom()"));
620        assert!(s.contains("integer()"));
621    }
622    #[test]
623    pub(super) fn test_beam_type_named_display() {
624        let t = BeamType::Named("my_type".into());
625        assert_eq!(t.to_string(), "my_type()");
626    }
627    #[test]
628    pub(super) fn test_beam_endian_display() {
629        assert_eq!(BeamEndian::Big.to_string(), "big");
630        assert_eq!(BeamEndian::Little.to_string(), "little");
631        assert_eq!(BeamEndian::Native.to_string(), "native");
632    }
633}