use beamr::atom::{Atom, AtomTable};
use beamr::interpreter::{ExecutionResult, InstructionOutcome, run};
use beamr::loader::decode::Operand;
use beamr::loader::{Instruction, load_beam_chunks, prepare_module};
use beamr::module::{Module, ModuleOrigin, ModuleRegistry};
use beamr::native::BifRegistryImpl;
use beamr::process::{CodePosition, ExitReason, Process};
use beamr::term::Term;
use beamr::term::boxed::write_tuple;
fn load_fixture(atoms: &AtomTable) -> Module {
let registry = ModuleRegistry::new();
let bifs = BifRegistryImpl::new();
let (module, _report) = prepare_module(
include_bytes!("fixtures/tagged_tuple_patterns.beam"),
atoms,
®istry,
&bifs,
)
.expect("tagged tuple fixture prepares");
module
}
fn call(module: &Module, atoms: &AtomTable, function: &str, args: &[Term]) -> Term {
let function = atoms.intern(function);
let arity = args.len() as u8;
let label = *module
.exports
.get(&(function, arity))
.expect("export exists");
let entry_ip = module
.code
.iter()
.position(|instruction| matches!(instruction, Instruction::Label { label: candidate } if *candidate == label))
.expect("export label exists");
let mut process = Process::new(1, 4096);
for (index, arg) in args.iter().enumerate() {
process.set_x_reg(index as u16, *arg);
}
process.set_code_position(Some(CodePosition {
module: module.name,
instruction_pointer: entry_ip,
}));
assert_eq!(
run(&mut process, module),
Ok(ExecutionResult::Exited(ExitReason::Normal))
);
process.x_reg(0)
}
fn dispatch_is_tagged_tuple(
module: &Module,
input: Term,
expected_arity: u64,
tag: Atom,
) -> InstructionOutcome {
let mut process = Process::new(1, 64);
process.set_x_reg(0, input);
let instruction = Instruction::IsTaggedTuple {
fail: Operand::Label(7),
value: Operand::X(0),
arity: Operand::Unsigned(expected_arity),
tag: Operand::Atom(Some(tag)),
};
beamr::interpreter::opcodes::dispatch(&mut process, module, &instruction, 1, None)
.expect("is_tagged_tuple dispatch")
}
#[test]
fn decoded_is_tagged_tuple_handler_checks_tuple_arity_and_first_atom() {
let module = Module {
name: Atom::OK,
generation: 0,
origin: ModuleOrigin::Preloaded,
exports: Default::default(),
label_index: [(7, 0)].into_iter().collect(),
code: vec![Instruction::Label { label: 7 }],
literals: Vec::new(),
constant_pool: Default::default(),
resolved_imports: Vec::new(),
lambdas: Vec::new(),
string_table: Vec::new(),
line_info: Vec::new(),
function_table: Vec::new(),
line_table: Vec::new(),
};
let mut ok_words = [0_u64; 3];
let mut error_words = [0_u64; 3];
let mut wrong_arity_words = [0_u64; 4];
let mut empty_words = [0_u64; 1];
let ok =
write_tuple(&mut ok_words, &[Term::atom(Atom::OK), Term::small_int(42)]).expect("ok tuple");
let error = write_tuple(
&mut error_words,
&[Term::atom(Atom::ERROR), Term::small_int(7)],
)
.expect("error tuple");
let wrong_arity = write_tuple(
&mut wrong_arity_words,
&[Term::atom(Atom::OK), Term::small_int(1), Term::small_int(2)],
)
.expect("wrong arity tuple");
let empty = write_tuple(&mut empty_words, &[]).expect("empty tuple");
assert_eq!(
dispatch_is_tagged_tuple(&module, ok, 2, Atom::OK),
InstructionOutcome::Continue
);
for (input, expected_arity) in [
(error, 2),
(wrong_arity, 2),
(Term::small_int(7), 2),
(empty, 0),
] {
assert!(matches!(
dispatch_is_tagged_tuple(&module, input, expected_arity, Atom::OK),
InstructionOutcome::Jump(CodePosition {
instruction_pointer: 0,
..
})
));
}
}
#[test]
fn compiled_record_pattern_fixture_uses_is_tagged_tuple() {
let atoms = AtomTable::new();
let parsed = load_beam_chunks(
include_bytes!("fixtures/tagged_tuple_patterns.beam"),
&atoms,
)
.expect("tagged tuple fixture parses");
assert!(
parsed
.instructions
.iter()
.any(|instruction| matches!(instruction, Instruction::IsTaggedTuple { .. })),
"fixture should contain opcode 159 decoded as Instruction::IsTaggedTuple"
);
}
#[test]
fn compiled_record_pattern_branches_execute_correctly() {
let atoms = AtomTable::with_common_atoms();
let module = load_fixture(&atoms);
let mut ok_words = [0_u64; 3];
let mut error_words = [0_u64; 3];
let ok =
write_tuple(&mut ok_words, &[Term::atom(Atom::OK), Term::small_int(42)]).expect("ok tuple");
let error = write_tuple(
&mut error_words,
&[Term::atom(Atom::ERROR), Term::small_int(7)],
)
.expect("error tuple");
assert_eq!(call(&module, &atoms, "match", &[ok]), Term::small_int(42));
assert_eq!(call(&module, &atoms, "match", &[error]), Term::small_int(7));
}
#[test]
fn compiled_nested_record_patterns_execute_correctly() {
let atoms = AtomTable::with_common_atoms();
let module = load_fixture(&atoms);
let mut inner_ok_words = [0_u64; 3];
let mut outer_ok_words = [0_u64; 3];
let mut inner_error_words = [0_u64; 3];
let mut outer_error_words = [0_u64; 3];
let inner_ok = write_tuple(
&mut inner_ok_words,
&[Term::atom(Atom::OK), Term::small_int(11)],
)
.expect("inner ok tuple");
let outer_ok = write_tuple(
&mut outer_ok_words,
&[Term::atom(atoms.intern("outer")), inner_ok],
)
.expect("outer ok tuple");
let inner_error = write_tuple(
&mut inner_error_words,
&[Term::atom(Atom::ERROR), Term::small_int(13)],
)
.expect("inner error tuple");
let outer_error = write_tuple(
&mut outer_error_words,
&[Term::atom(atoms.intern("outer")), inner_error],
)
.expect("outer error tuple");
assert_eq!(
call(&module, &atoms, "nested", &[outer_ok]),
Term::small_int(11)
);
assert_eq!(
call(&module, &atoms, "nested", &[outer_error]),
Term::small_int(13)
);
}