use crate::heap_print::*; pub use crate::machine::heap::*; pub use crate::machine::machine_state::*; pub use crate::machine::streams::*; pub use crate::machine::*; pub use crate::parser::ast::*; #[cfg(test)] use crate::machine::copier::CopierTarget; use crate::read::TermWriteResult; #[cfg(test)] use std::ops::{Deref, DerefMut, Index, IndexMut, Range}; // a mini-WAM for test purposes. pub struct MockWAM { pub machine_st: MachineState, pub op_dir: OpDir, //pub flags: MachineFlags, } #[allow(dead_code)] impl MockWAM { pub fn new() -> Self { let op_dir = default_op_dir(); Self { machine_st: MachineState::new(), op_dir, } } pub fn write_parsed_term_to_heap( &mut self, input_stream: Stream, ) -> Result { self.machine_st.read(input_stream, &self.op_dir) } pub fn parse_and_write_parsed_term_to_heap( &mut self, term_string: &'static str, ) -> Result { let stream = Stream::from_static_string(term_string, &mut self.machine_st.arena); self.write_parsed_term_to_heap(stream) } pub fn parse_and_print_term( &mut self, term_string: &'static str, ) -> Result { let term_write_result = self.parse_and_write_parsed_term_to_heap(term_string)?; print_heap_terms(&self.machine_st.heap, term_write_result.heap_loc); let mut printer = HCPrinter::new( &mut self.machine_st.heap, &mut self.machine_st.stack, &self.machine_st.arena, &self.op_dir, PrinterOutputter::new(), term_write_result.heap_loc, ); printer.var_names = term_write_result .var_dict .into_iter() .map(|(var, cell)| match var { VarKey::VarPtr(var) => (cell, var.clone()), VarKey::AnonVar(_) => (cell, VarPtr::from(var.to_string())), }) .collect(); Ok(printer.print().result()) } } impl Default for MockWAM { fn default() -> Self { Self::new() } } #[cfg(test)] pub struct TermCopyingMockWAM<'a> { pub wam: &'a mut MockWAM, } #[cfg(test)] impl<'a> Index for TermCopyingMockWAM<'a> { type Output = HeapCellValue; #[inline] fn index(&self, index: usize) -> &HeapCellValue { &self.wam.machine_st.heap[index] } } #[cfg(test)] impl<'a> IndexMut for TermCopyingMockWAM<'a> { #[inline] fn index_mut(&mut self, index: usize) -> &mut HeapCellValue { &mut self.wam.machine_st.heap[index] } } #[cfg(test)] impl<'a> Deref for TermCopyingMockWAM<'a> { type Target = MockWAM; #[inline] fn deref(&self) -> &Self::Target { self.wam } } #[cfg(test)] impl<'a> DerefMut for TermCopyingMockWAM<'a> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.wam } } #[cfg(test)] impl<'a> CopierTarget for TermCopyingMockWAM<'a> { fn store(&self, val: HeapCellValue) -> HeapCellValue { read_heap_cell!(val, (HeapCellValueTag::AttrVar | HeapCellValueTag::Var, h) => { self.wam.machine_st.heap[h] } (HeapCellValueTag::StackVar, s) => { self.wam.machine_st.stack[s] } _ => { val } ) } fn deref(&self, mut val: HeapCellValue) -> HeapCellValue { loop { let value = self.store(val); if value.is_var() && value != val { val = value; continue; } return val; } } fn push_attr_var_queue(&mut self, attr_var_loc: usize) { self.wam .machine_st .attr_var_init .attr_var_queue .push(attr_var_loc); } fn stack(&mut self) -> &mut Stack { &mut self.wam.machine_st.stack } fn threshold(&self) -> usize { self.wam.machine_st.heap.cell_len() } #[inline(always)] fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result { self.wam.machine_st.heap.copy_pstr_within(pstr_loc) } #[inline(always)] fn as_slice_from<'b>(&'b self, from: usize) -> Box + 'b> { Box::new(self.wam.machine_st.heap.as_slice()[from..].iter().cloned()) } #[inline(always)] fn reserve(&mut self, num_cells: usize) -> Result, AllocError> { self.wam.machine_st.heap.reserve(num_cells) } #[inline(always)] fn copy_slice_to_end(&mut self, bounds: Range) -> Result<(), AllocError> { self.wam.machine_st.heap.copy_slice_to_end(bounds) } } #[cfg(test)] pub fn all_cells_marked_and_unforwarded(heap: &Heap, offset: usize) { for curr_idx in offset..heap.cell_len() { let cell = heap[curr_idx]; assert!( cell.get_mark_bit(), "cell {cell:?} at index {curr_idx} is not marked" ); assert!( !cell.get_forwarding_bit(), "cell {cell:?} at index {curr_idx} is forwarded" ); } } #[cfg(test)] pub fn unmark_all_cells(heap: &mut Heap, offset: usize) { for idx in offset..heap.cell_len() { heap[idx].set_mark_bit(false); } } #[cfg(test)] pub fn all_cells_unmarked(iter: &impl SizedHeap) { let mut idx = 0; let cell_len = iter.cell_len(); while idx < cell_len { let curr_idx = idx; idx += 1; let cell = iter[curr_idx]; assert!( !cell.get_mark_bit(), "cell {cell:?} at index {curr_idx} is still marked" ); } } #[cfg(test)] pub(crate) fn write_parsed_term_to_heap( machine_st: &mut MachineState, input_stream: Stream, op_dir: &OpDir, ) -> Result { machine_st.read(input_stream, op_dir) } #[cfg(test)] pub(crate) fn parse_and_write_parsed_term_to_heap( machine_st: &mut MachineState, term_string: &'static str, op_dir: &OpDir, ) -> Result { let stream = Stream::from_static_string(term_string, &mut machine_st.arena); write_parsed_term_to_heap(machine_st, stream, op_dir) } impl Machine { /// For use in tests. #[allow(clippy::unbuffered_bytes)] pub fn test_load_file(&mut self, file: &str) -> Vec { let stream = Stream::from_owned_string( std::fs::read_to_string(AsRef::::as_ref(file)).unwrap(), &mut self.machine_st.arena, ); self.load_file(file, stream); self.user_output.bytes().map(|b| b.unwrap()).collect() } /// For use in tests. #[allow(clippy::unbuffered_bytes)] pub fn test_load_string(&mut self, code: &str) -> Vec { let stream = Stream::from_owned_string(code.to_owned(), &mut self.machine_st.arena); self.load_file("", stream); self.user_output.bytes().map(|b| b.unwrap()).collect() } } #[cfg(test)] mod tests { use super::*; use crate::functor_macro::FunctorElement; #[test] fn unify_tests() { let mut wam = MachineState::new(); let mut op_dir = default_op_dir(); op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX)); op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX)); op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(500, YFX)); op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX)); op_dir.insert((atom!("="), Fixity::In), OpDesc::build_with(700, XFX)); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(b,a).", &op_dir).unwrap(); unify!( wam, str_loc_as_cell!(0), str_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(wam.fail); } all_cells_unmarked(&wam.heap); wam.fail = false; wam.heap.clear(); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(b,b).", &op_dir).unwrap(); unify!( wam, str_loc_as_cell!(1), heap_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(!wam.fail); } all_cells_unmarked(&wam.heap); wam.fail = false; wam.heap.clear(); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(f(A),Y).", &op_dir).unwrap(); unify!( wam, heap_loc_as_cell!(0), heap_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(!wam.fail); } all_cells_unmarked(&wam.heap); wam.fail = false; wam.heap.clear(); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(f(A),Y).", &op_dir).unwrap(); unify!( wam, heap_loc_as_cell!(0), heap_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(!wam.fail); } all_cells_unmarked(&wam.heap); wam.fail = false; wam.heap.clear(); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(f(A),A).", &op_dir).unwrap(); unify!( wam, heap_loc_as_cell!(0), heap_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(!wam.fail); } all_cells_unmarked(&wam.heap); wam.fail = false; wam.heap.clear(); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(A,f(A)).", &op_dir).unwrap(); all_cells_unmarked(&wam.heap); unify!( wam, heap_loc_as_cell!(0), heap_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(!wam.fail); } all_cells_unmarked(&wam.heap); wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_pstr("this is a string"); // 0 let h = section.cell_len(); assert_eq!(h, 3); section.push_cell(heap_loc_as_cell!(h)); // 3 section.push_pstr("this is a string"); // 4 let h = section.cell_len(); assert_eq!(h + 1, 8); section.push_cell(pstr_loc_as_cell!(heap_index!(h + 1))); // 7 section.push_pstr("this is a string"); // 8 section.push_cell(pstr_loc_as_cell!(heap_index!(h + 1))); }); unify!(wam, pstr_loc_as_cell!(0), pstr_loc_as_cell!(heap_index!(4))); assert!(!wam.fail); assert_eq!( wam.heap .slice_to_str(heap_index!(0), "this is a string".len()), "this is a string" ); assert_eq!(wam.heap[3], pstr_loc_as_cell!(heap_index!(8))); assert_eq!( wam.heap .slice_to_str(heap_index!(4), "this is a string".len()), "this is a string" ); assert_eq!(wam.heap[7], pstr_loc_as_cell!(heap_index!(8))); assert_eq!( wam.heap .slice_to_str(heap_index!(8), "this is a string".len()), "this is a string" ); assert_eq!(wam.heap[11], pstr_loc_as_cell!(heap_index!(8))); wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_cell(list_loc_as_cell!(1)); section.push_cell(atom_as_cell!(atom!("a"))); section.push_cell(list_loc_as_cell!(3)); section.push_cell(atom_as_cell!(atom!("b"))); section.push_cell(heap_loc_as_cell!(0)); section.push_cell(list_loc_as_cell!(6)); section.push_cell(atom_as_cell!(atom!("a"))); section.push_cell(list_loc_as_cell!(8)); section.push_cell(atom_as_cell!(atom!("b"))); section.push_cell(heap_loc_as_cell!(5)); }); unify!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(5)); assert!(!wam.fail); all_cells_unmarked(&wam.heap); wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_cell(list_loc_as_cell!(1)); section.push_cell(atom_as_cell!(atom!("a"))); section.push_cell(list_loc_as_cell!(3)); section.push_cell(atom_as_cell!(atom!("b"))); section.push_cell(heap_loc_as_cell!(0)); section.push_cell(list_loc_as_cell!(6)); section.push_cell(atom_as_cell!(atom!("a"))); section.push_cell(list_loc_as_cell!(8)); section.push_cell(atom_as_cell!(atom!("c"))); section.push_cell(heap_loc_as_cell!(5)); }); unify!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(5)); assert!(wam.fail); wam.fail = false; all_cells_unmarked(&wam.heap); wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_cell(list_loc_as_cell!(1)); section.push_cell(atom_as_cell!(atom!("a"))); section.push_cell(list_loc_as_cell!(3)); section.push_cell(atom_as_cell!(atom!("b"))); section.push_cell(heap_loc_as_cell!(5)); section.push_cell(list_loc_as_cell!(6)); section.push_cell(atom_as_cell!(atom!("a"))); section.push_cell(list_loc_as_cell!(8)); section.push_cell(atom_as_cell!(atom!("b"))); section.push_cell(heap_loc_as_cell!(0)); }); unify!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(5)); assert!(!wam.fail); all_cells_unmarked(&wam.heap); wam.heap.clear(); { let term_write_result_1 = parse_and_write_parsed_term_to_heap(&mut wam, "X = g(X,y).", &op_dir).unwrap(); print_heap_terms(&wam.heap, term_write_result_1.heap_loc); unify!(wam, heap_loc_as_cell!(2), str_loc_as_cell!(4)); assert_eq!(wam.heap[2], str_loc_as_cell!(4)); } } #[test] fn test_unify_with_occurs_check() { let mut wam = MachineState::new(); let mut op_dir = default_op_dir(); op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX)); op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX)); op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX)); op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX)); { parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap(); let term_write_result_2 = parse_and_write_parsed_term_to_heap(&mut wam, "f(A,f(A)).", &op_dir).unwrap(); all_cells_unmarked(&wam.heap); unify_with_occurs_check!( wam, str_loc_as_cell!(0), str_loc_as_cell!(term_write_result_2.heap_loc) ); assert!(wam.fail); } } #[test] fn test_term_compare() { use std::cmp::Ordering; let mut wam = MachineState::new(); // clear the heap of resource error data etc wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_cell(heap_loc_as_cell!(0)); section.push_cell(heap_loc_as_cell!(1)); }); assert_eq!( compare_term_test!(wam, wam.heap[0], wam.heap[1]), Some(Ordering::Less) ); assert_eq!( compare_term_test!(wam, wam.heap[1], wam.heap[0]), Some(Ordering::Greater) ); assert_eq!( compare_term_test!(wam, wam.heap[0], wam.heap[0]), Some(Ordering::Equal) ); assert_eq!( compare_term_test!(wam, wam.heap[1], wam.heap[1]), Some(Ordering::Equal) ); let cstr_cell = wam.heap.allocate_cstr("string").unwrap(); assert_eq!( compare_term_test!(wam, atom_as_cell!(atom!("atom")), cstr_cell), Some(Ordering::Less) ); assert_eq!( compare_term_test!( wam, atom_as_cell!(atom!("atom")), atom_as_cell!(atom!("atom")) ), Some(Ordering::Equal) ); assert_eq!( compare_term_test!( wam, atom_as_cell!(atom!("atom")), atom_as_cell!(atom!("aaa")) ), Some(Ordering::Greater) ); assert_eq!( compare_term_test!( wam, fixnum_as_cell!(Fixnum::build_with(6)), heap_loc_as_cell!(1) ), Some(Ordering::Greater) ); wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_cell(atom_as_cell!(atom!("f"), 1)); section.push_cell(heap_loc_as_cell!(1)); }); assert_eq!( compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(0)), Some(Ordering::Equal) ); assert_eq!( compare_term_test!(wam, heap_loc_as_cell!(0), atom_as_cell!(atom!("a"))), Some(Ordering::Greater) ); wam.heap.clear(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { // [1,2,3] section.push_cell(list_loc_as_cell!(1)); section.push_cell(fixnum_as_cell!(Fixnum::build_with(1))); section.push_cell(list_loc_as_cell!(3)); section.push_cell(fixnum_as_cell!(Fixnum::build_with(2))); section.push_cell(list_loc_as_cell!(5)); section.push_cell(fixnum_as_cell!(Fixnum::build_with(3))); section.push_cell(empty_list_as_cell!()); // [1,2] section.push_cell(list_loc_as_cell!(8)); section.push_cell(fixnum_as_cell!(Fixnum::build_with(1))); section.push_cell(list_loc_as_cell!(10)); section.push_cell(fixnum_as_cell!(Fixnum::build_with(2))); section.push_cell(empty_list_as_cell!()); }); assert_eq!( compare_term_test!(wam, heap_loc_as_cell!(7), heap_loc_as_cell!(7)), Some(Ordering::Equal) ); assert_eq!( compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(7)), Some(Ordering::Greater) ); assert_eq!( compare_term_test!(wam, empty_list_as_cell!(), heap_loc_as_cell!(7)), Some(Ordering::Less) ); assert_eq!( compare_term_test!( wam, empty_list_as_cell!(), fixnum_as_cell!(Fixnum::build_with(1)) ), Some(Ordering::Greater) ); let cstr_cell = wam.heap.allocate_cstr("string").unwrap(); assert_eq!( compare_term_test!(wam, empty_list_as_cell!(), cstr_cell), Some(Ordering::Less) ); assert_eq!( compare_term_test!(wam, empty_list_as_cell!(), atom_as_cell!(atom!("atom"))), Some(Ordering::Less) ); assert_eq!( compare_term_test!(wam, atom_as_cell!(atom!("atom")), empty_list_as_cell!()), Some(Ordering::Greater) ); let one_p_one = HeapCellValue::from(float_alloc!(1.1, &mut wam.arena)); assert_eq!( compare_term_test!(wam, one_p_one, fixnum_as_cell!(Fixnum::build_with(1))), Some(Ordering::Less) ); assert_eq!( compare_term_test!(wam, fixnum_as_cell!(Fixnum::build_with(1)), one_p_one), Some(Ordering::Greater) ); } #[test] fn is_cyclic_term_tests() { let mut wam = MachineState::new(); let mut writer = wam.heap.reserve(96).unwrap(); writer.write_with(|section| { section.push_cell(atom_as_cell!(atom!("f"))); section.push_cell(fixnum_as_cell!(Fixnum::build_with(555))); section.push_cell(heap_loc_as_cell!(0)); }); assert!(!wam.is_cyclic_term(0)); assert!(!wam.is_cyclic_term(1)); assert!(!wam.is_cyclic_term(2)); all_cells_unmarked(&wam.heap); wam.heap.clear(); let mut functor_writer = Heap::functor_writer(functor!( atom!("f"), [atom_as_cell((atom!("a"))), atom_as_cell((atom!("b")))] )); functor_writer(&mut wam.heap).unwrap(); let h = wam.heap.cell_len(); wam.heap.push_cell(str_loc_as_cell!(0)).unwrap(); assert!(!wam.is_cyclic_term(h)); all_cells_unmarked(&wam.heap); assert!(!wam.is_cyclic_term(1)); all_cells_unmarked(&wam.heap); assert!(!wam.is_cyclic_term(2)); all_cells_unmarked(&wam.heap); wam.heap[2] = str_loc_as_cell!(0); print_heap_terms(&wam.heap, 0); assert!(wam.is_cyclic_term(2)); all_cells_unmarked(&wam.heap); wam.heap[2] = atom_as_cell!(atom!("b")); wam.heap[1] = str_loc_as_cell!(0); assert!(wam.is_cyclic_term(1)); all_cells_unmarked(&wam.heap); wam.heap.clear(); let h = wam.heap.cell_len(); wam.heap.allocate_cstr("a string").unwrap(); assert!(!wam.is_cyclic_term(h)); } }