use std::collections::HashMap;
use crate::atom::Atom;
use crate::error::ExecError;
use crate::interpreter::InstructionOutcome;
use crate::interpreter::opcodes::binary::{binary_op, heap_slice};
use crate::loader::decode::BinaryOp;
use crate::loader::decode::compact::Operand;
use crate::loader::{Instruction, Literal};
use crate::module::{Module, ModuleOrigin};
use crate::process::{CodePosition, Process};
use crate::term::Term;
use crate::term::binary::{packed_word_count, write_binary};
use crate::term::binary_ref::BinaryRef;
const TYPE_INTEGER: u32 = 75;
const TYPE_BINARY: u32 = 71;
const TYPE_STRING: u32 = 73;
const ALL: u32 = 70;
const LITTLE: u32 = 92;
fn module(literals: Vec<Literal>, string_table: Vec<u8>) -> Module {
let code = vec![Instruction::Label { label: 9 }];
let constant_pool =
crate::constant_pool::materialise_literals(&literals, None).expect("literal pool");
Module {
name: Atom::OK,
generation: 0,
origin: ModuleOrigin::Preloaded,
exports: HashMap::new(),
label_index: [(9, 0)].into_iter().collect(),
code,
literals,
constant_pool,
resolved_imports: Vec::new(),
lambdas: Vec::new(),
string_table,
function_table: Vec::new(),
line_table: Vec::new(),
line_info: Vec::new(),
}
}
fn binary_term(process: &mut Process, bytes: &[u8]) -> Term {
let words = 2 + packed_word_count(bytes.len());
let ptr = process.heap_mut().alloc(words).expect("test heap fits");
let heap = heap_slice(ptr, words);
write_binary(heap, bytes).expect("test binary fits")
}
fn atom(index: u32) -> Operand {
Operand::Atom(Some(Atom::new(index)))
}
fn segment(
type_atom: Operand,
unit: u64,
flags: Operand,
source: Operand,
size: Operand,
) -> Vec<Operand> {
vec![
type_atom,
Operand::Unsigned(1),
Operand::Unsigned(unit),
flags,
source,
size,
]
}
fn create_bin(
process: &mut Process,
module: &Module,
fail: u32,
segments: Vec<Vec<Operand>>,
) -> Result<InstructionOutcome, ExecError> {
let fields: Vec<Operand> = segments.into_iter().flatten().collect();
binary_op(
process,
module,
BinaryOp::BsCreateBin,
&[
Operand::Label(fail),
Operand::Unsigned(0),
Operand::Unsigned(1),
Operand::Unsigned(1),
Operand::X(0),
Operand::List(fields),
],
)
}
fn result_bytes(process: &Process) -> Vec<u8> {
BinaryRef::new(process.x_reg(0))
.expect("binary result")
.as_bytes()
.to_vec()
}
#[test]
fn bs_create_bin_integer_register_segment() {
let mut process = Process::new(1, 64);
let module = module(Vec::new(), Vec::new());
process.set_x_reg(1, Term::small_int(65));
let segments = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(8),
)];
assert_eq!(
create_bin(&mut process, &module, 0, segments),
Ok(InstructionOutcome::Continue)
);
assert_eq!(result_bytes(&process), vec![65]);
}
#[test]
fn bs_create_bin_integer_endianness_from_literal_flag_list() {
let literals = vec![Literal::List(
vec![Literal::Atom(Atom::new(LITTLE))],
Box::new(Literal::Nil),
)];
let module = module(literals, Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(0x4142));
let little = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Literal(0),
Operand::X(1),
Operand::Integer(16),
)];
create_bin(&mut process, &module, 0, little).expect("little-endian segment");
assert_eq!(result_bytes(&process), vec![0x42, 0x41]);
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(0x4142));
let big = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(16),
)];
create_bin(&mut process, &module, 0, big).expect("big-endian segment");
assert_eq!(result_bytes(&process), vec![0x41, 0x42]);
}
#[test]
fn bs_create_bin_integer_truncates_and_sign_extends() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(300));
let truncated = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(8),
)];
create_bin(&mut process, &module, 0, truncated).expect("truncating segment");
assert_eq!(result_bytes(&process), vec![44]);
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(-2));
let negative = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(16),
)];
create_bin(&mut process, &module, 0, negative).expect("negative segment");
assert_eq!(result_bytes(&process), vec![0xff, 0xfe]);
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(-1));
let wide = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(128),
)];
create_bin(&mut process, &module, 0, wide).expect("sign-extended segment");
assert_eq!(result_bytes(&process), vec![0xff; 16]);
}
#[test]
fn bs_create_bin_append_and_binary_all_segments_concatenate() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
let head = binary_term(&mut process, b"hello");
let tail = binary_term(&mut process, b" world");
process.set_x_reg(1, head);
process.set_x_reg(2, tail);
let segments = vec![
segment(
Operand::Atom(Some(Atom::APPEND)),
8,
Operand::Atom(None),
Operand::X(1),
atom(ALL),
),
segment(
atom(TYPE_BINARY),
8,
Operand::Atom(None),
Operand::TypedRegister {
register: Box::new(Operand::X(2)),
type_index: 1,
},
atom(ALL),
),
];
create_bin(&mut process, &module, 0, segments).expect("binary concat");
assert_eq!(result_bytes(&process), b"hello world");
}
#[test]
fn bs_create_bin_string_table_segment() {
let module = module(Vec::new(), b"lit!".to_vec());
let mut process = Process::new(1, 64);
let segments = vec![segment(
atom(TYPE_STRING),
8,
Operand::Atom(None),
Operand::Unsigned(0),
Operand::Integer(3),
)];
create_bin(&mut process, &module, 0, segments).expect("string segment");
assert_eq!(result_bytes(&process), b"lit");
}
#[test]
fn bs_create_bin_literal_binary_source_with_register_size() {
let module = module(vec![Literal::Binary(b"abcdef".to_vec())], Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(3));
let segments = vec![segment(
atom(TYPE_BINARY),
8,
Operand::Atom(None),
Operand::Literal(0),
Operand::X(1),
)];
create_bin(&mut process, &module, 0, segments).expect("literal prefix");
assert_eq!(result_bytes(&process), b"abc");
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(9));
let segments = vec![segment(
atom(TYPE_BINARY),
8,
Operand::Atom(None),
Operand::Literal(0),
Operand::X(1),
)];
assert_eq!(
create_bin(&mut process, &module, 0, segments),
Err(ExecError::Badarg)
);
}
#[test]
fn bs_create_bin_sized_binary_register_source() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
let source = binary_term(&mut process, b"abcdef");
process.set_x_reg(1, source);
process.set_x_reg(2, Term::small_int(3));
let segments = vec![segment(
atom(TYPE_BINARY),
8,
Operand::Atom(None),
Operand::X(1),
Operand::X(2),
)];
create_bin(&mut process, &module, 0, segments).expect("sized binary");
assert_eq!(result_bytes(&process), b"abc");
}
#[test]
fn bs_create_bin_utf8_segment_encodes_codepoint() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(0x20AC));
let segments = vec![segment(
Operand::Atom(Some(Atom::UTF8)),
0,
Operand::Atom(None),
Operand::X(1),
Operand::Atom(Some(Atom::UNDEFINED)),
)];
create_bin(&mut process, &module, 0, segments).expect("utf8 segment");
assert_eq!(result_bytes(&process), vec![0xe2, 0x82, 0xac]);
}
#[test]
fn bs_create_bin_utf8_surrogate_raises_badarg_or_jumps() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(0xD800));
let surrogate = |process: &mut Process, fail| {
create_bin(
process,
&module,
fail,
vec![segment(
Operand::Atom(Some(Atom::UTF8)),
0,
Operand::Atom(None),
Operand::X(1),
Operand::Atom(Some(Atom::UNDEFINED)),
)],
)
};
assert_eq!(surrogate(&mut process, 0), Err(ExecError::Badarg));
assert_eq!(
surrogate(&mut process, 9),
Ok(InstructionOutcome::Jump(CodePosition {
module: Atom::OK,
instruction_pointer: 0,
}))
);
}
#[test]
fn bs_create_bin_float_segments() {
let module = module(
vec![
Literal::Float(1.5),
Literal::List(
vec![Literal::Atom(Atom::new(LITTLE))],
Box::new(Literal::Nil),
),
],
Vec::new(),
);
let mut process = Process::new(1, 64);
let segments = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::Literal(0),
Operand::Integer(64),
)];
create_bin(&mut process, &module, 0, segments).expect("float64 big");
assert_eq!(result_bytes(&process), 1.5_f64.to_bits().to_be_bytes());
let mut process = Process::new(1, 64);
let segments = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Literal(1),
Operand::Literal(0),
Operand::Integer(32),
)];
create_bin(&mut process, &module, 0, segments).expect("float32 little");
assert_eq!(result_bytes(&process), 1.5_f32.to_bits().to_le_bytes());
}
#[test]
fn bs_create_bin_bit_level_segments_pack_subbyte_values() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(5));
let segments = vec![
segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(3),
),
segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::Integer(0),
Operand::Integer(5),
),
];
create_bin(&mut process, &module, 0, segments).expect("bit segments");
assert_eq!(result_bytes(&process), vec![0b1010_0000]);
}
#[test]
fn bs_create_bin_non_byte_total_raises_badarg() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
process.set_x_reg(1, Term::small_int(5));
let segments = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(3),
)];
assert_eq!(
create_bin(&mut process, &module, 0, segments),
Err(ExecError::Badarg)
);
}
#[test]
fn bs_create_bin_non_numeric_source_raises_badarg() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
let segments = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::Atom(Some(Atom::OK)),
Operand::Integer(8),
)];
assert_eq!(
create_bin(&mut process, &module, 0, segments),
Err(ExecError::Badarg)
);
}
#[test]
fn bs_create_bin_collects_and_grows_when_heap_is_full() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 2);
process.set_x_reg(1, Term::small_int(65));
let segments = vec![segment(
atom(TYPE_INTEGER),
1,
Operand::Atom(None),
Operand::X(1),
Operand::Integer(8),
)];
create_bin(&mut process, &module, 0, segments)
.expect("create_bin grows the heap instead of failing");
assert_eq!(result_bytes(&process), vec![65]);
}
#[test]
fn bs_create_bin_malformed_segment_list_is_invalid_operand() {
let module = module(Vec::new(), Vec::new());
let mut process = Process::new(1, 64);
let result = binary_op(
&mut process,
&module,
BinaryOp::BsCreateBin,
&[
Operand::Label(0),
Operand::Unsigned(0),
Operand::Unsigned(1),
Operand::Unsigned(1),
Operand::X(0),
Operand::List(vec![Operand::Atom(None), Operand::Unsigned(1)]),
],
);
assert_eq!(
result,
Err(ExecError::InvalidOperand("bs_create_bin segment"))
);
}