use crate::prolog_parser::ast::*;
use crate::prolog_parser::tabled_rc::*;

use crate::clause_types::*;
use crate::forms::*;
use crate::heap_iter::*;
use crate::instructions::*;
use crate::machine::INTERRUPT;
use crate::machine::attributed_variables::*;
use crate::machine::code_repo::CodeRepo;
use crate::machine::copier::*;
use crate::machine::heap::*;
use crate::machine::machine_errors::*;
use crate::machine::machine_indices::*;
use crate::machine::machine_state::*;
use crate::machine::stack::*;
use crate::machine::streams::*;
use crate::ordered_float::*;
use crate::rug::Integer;

use crate::indexmap::{IndexMap, IndexSet};

use std::cmp::Ordering;
use std::convert::TryFrom;
use std::rc::Rc;

macro_rules! try_or_fail {
    ($s:ident, $e:expr) => {{
        match $e {
            Ok(val) => val,
            Err(msg) => {
                $s.throw_exception(msg);
                return;
            }
        }
    }};
}

impl MachineState {
    pub(crate)
    fn new() -> Self {
        MachineState {
            s: HeapPtr::default(),
            p: CodePtr::default(),
            b: 0,
            b0: 0,
            e: 0,
            num_of_args: 0,
            cp: LocalCodePtr::default(),
            attr_var_init: AttrVarInitializer::new(0, 0),
            fail: false,
            heap: Heap::new(),
            mode: MachineMode::Write,
            stack: Stack::new(),
            registers: vec![Addr::HeapCell(0); MAX_ARITY + 1], // self.registers[0] is never used.
            trail: vec![],
            tr: 0,
            hb: 0,
            block: 0,
            ball: Ball::new(),
            lifted_heap: Heap::new(),
            interms: vec![Number::default(); 256],
            last_call: false,
            heap_locs: HeapVarDict::new(),
            flags: MachineFlags::default(),
            at_end_of_expansion: false
        }
    }

    pub(crate)
    fn with_small_heap() -> Self {
        MachineState {
            s: HeapPtr::default(),
            p: CodePtr::default(),
            b: 0,
            b0: 0,
            e: 0,
            num_of_args: 0,
            cp: LocalCodePtr::default(),
            attr_var_init: AttrVarInitializer::new(0, 0),
            fail: false,
            heap: Heap::new(),
            mode: MachineMode::Write,
            stack: Stack::new(),
            registers: vec![Addr::HeapCell(0); MAX_ARITY + 1], // self.registers[0] is never used.
            trail: vec![],
            tr: 0,
            hb: 0,
            block: 0,
            ball: Ball::new(),
            lifted_heap: Heap::new(),
            interms: vec![Number::default(); 0],
            last_call: false,
            heap_locs: HeapVarDict::new(),
            flags: MachineFlags::default(),
            at_end_of_expansion: false
        }
    }

    #[inline]
    pub fn machine_flags(&self) -> MachineFlags {
        self.flags
    }

    pub(crate)
    fn store(&self, addr: Addr) -> Addr {
        match addr {
            Addr::AttrVar(h) | Addr::HeapCell(h) => {
                self.heap[h].as_addr(h)
            }
            Addr::StackCell(fr, sc) => {
                self.stack.index_and_frame(fr)[sc]
            }
            Addr::PStrLocation(h, n) => {
                if let &HeapCellValue::PartialString(ref pstr, has_tail) = &self.heap[h] {
                    if !pstr.at_end(n) {
                        Addr::PStrLocation(h, n)
                    } else if has_tail {
                        Addr::HeapCell(h + 1)
                    } else {
                        Addr::EmptyList
                    }
                } else {
                    unreachable!()
                }
            }
            addr => {
                addr
            }
        }
    }

    pub(crate)
    fn deref(&self, mut addr: Addr) -> Addr {
        loop {
            let value = self.store(addr);

            if value.is_ref() && value != addr {
                addr = value;
                continue;
            }

            return addr;
        }
    }

    fn bind_attr_var(&mut self, h: usize, addr: Addr) {
        match addr.as_var() {
            Some(Ref::HeapCell(hc)) => {
                self.heap[hc] = HeapCellValue::Addr(Addr::AttrVar(h));
                self.trail(TrailRef::Ref(Ref::HeapCell(hc)));
            }
            Some(Ref::StackCell(fr, sc)) => {
                self.stack.index_and_frame_mut(fr)[sc] = Addr::AttrVar(h);
                self.trail(TrailRef::Ref(Ref::StackCell(fr, sc)));
            }
            _ => {
                self.push_attr_var_binding(h, addr);
                self.heap[h] = HeapCellValue::Addr(addr);
                self.trail(TrailRef::Ref(Ref::AttrVar(h)));
            }
        }
    }

    pub(super)
    fn bind(&mut self, r1: Ref, a2: Addr) {
        let t1 = self.store(r1.as_addr());
        let t2 = self.store(a2);

        if t1.is_ref() && (!t2.is_ref() || a2 < r1) {
            match r1 {
                Ref::StackCell(fr, sc) => {
                    self.stack.index_and_frame_mut(fr)[sc] = t2;
                }
                Ref::HeapCell(h) => {
                    self.heap[h] = HeapCellValue::Addr(t2);
                }
                Ref::AttrVar(h) => {
                    return self.bind_attr_var(h, t2);
                }
            };

            self.trail(TrailRef::from(r1));
        } else {
            match a2.as_var() {
                Some(Ref::StackCell(fr, sc)) => {
                    self.stack.index_and_frame_mut(fr)[sc] = t1;
                    self.trail(TrailRef::Ref(Ref::StackCell(fr, sc)));
                }
                Some(Ref::HeapCell(h)) => {
                    self.heap[h] = HeapCellValue::Addr(t1);
                    self.trail(TrailRef::Ref(Ref::HeapCell(h)));
                }
                Some(Ref::AttrVar(h)) => {
                    self.bind_attr_var(h, t1);
                }
                None => {
                }
            }
        }
    }

    fn bind_with_occurs_check(&mut self, r: Ref, addr: Addr) {
        let mut fail = false;

        for addr in self.acyclic_pre_order_iter(addr) {
            if let Some(inner_r) = addr.as_var() {
                if r == inner_r {
                    fail = true;
                    break;
                }
            }
        }

        self.fail = fail;
        self.bind(r, addr);
    }

    pub(super)
    fn unify_with_occurs_check(&mut self, a1: Addr, a2: Addr) {
        let mut pdl = vec![a1, a2];
        let mut tabu_list: IndexSet<(Addr, Addr)> = IndexSet::new();

        self.fail = false;

        while !(pdl.is_empty() || self.fail) {
            let d1 = self.deref(pdl.pop().unwrap());
            let d2 = self.deref(pdl.pop().unwrap());

            if d1 != d2 {
                let d1 = self.store(d1);
                let d2 = self.store(d2);

                if tabu_list.contains(&(d1, d2)) {
                    continue;
                } else {
                    tabu_list.insert((d1, d2));
                }

                match (d1, d2) {
                    (Addr::AttrVar(h), addr) | (addr, Addr::AttrVar(h)) => {
                        self.bind_with_occurs_check(Ref::AttrVar(h), addr)
                    }
                    (Addr::HeapCell(h), addr) | (addr, Addr::HeapCell(h)) => {
                        self.bind_with_occurs_check(Ref::HeapCell(h), addr)
                    }
                    (Addr::StackCell(fr, sc), addr) | (addr, Addr::StackCell(fr, sc)) => {
                        self.bind_with_occurs_check(Ref::StackCell(fr, sc), addr)
                    }
                    (Addr::Lis(a1), Addr::Str(a2)) | (Addr::Str(a2), Addr::Lis(a1)) => {
                        if let &HeapCellValue::NamedStr(n2, ref f2, _) = &self.heap[a2] {
                            if f2.as_str() == "." && n2 == 2 {
                                pdl.push(Addr::HeapCell(a1));
                                pdl.push(Addr::HeapCell(a2 + 1));

                                pdl.push(Addr::HeapCell(a1 + 1));
                                pdl.push(Addr::HeapCell(a2 + 2));

                                continue;
                            }
                        }

                        self.fail = true;
                    }
                    (Addr::PStrLocation(h, n), Addr::Lis(l)) |
                    (Addr::Lis(l), Addr::PStrLocation(h, n)) => {
                        if let HeapCellValue::PartialString(ref pstr, _) = &self.heap[h] {
                            if let Some(c) = pstr.range_from(n ..).next() {
                                pdl.push(Addr::PStrLocation(h, n + c.len_utf8()));
                                pdl.push(Addr::HeapCell(l + 1));

                                pdl.push(Addr::Char(c));
                                pdl.push(Addr::HeapCell(l));
                            } else {
                                unreachable!()
                            }
                        } else {
                            unreachable!()
                        }
                    }
                    (Addr::PStrLocation(h1, n1), Addr::PStrLocation(h2, n2)) => {
                        if let &HeapCellValue::PartialString(ref pstr1, has_tail_1) = &self.heap[h1] {
                            if let &HeapCellValue::PartialString(ref pstr2, has_tail_2) = &self.heap[h2] {
                                let pstr1_s = pstr1.as_str_from(n1);
                                let pstr2_s = pstr2.as_str_from(n2);

                                let m_len =
                                    if pstr1_s.starts_with(pstr2_s) {
                                        pstr2_s.len()
                                    } else if pstr2_s.starts_with(pstr1_s) {
                                        pstr1_s.len()
                                    } else {
                                        self.fail = true;
                                        return;
                                    };

                                if pstr1.at_end(n1 + m_len) {
                                    if has_tail_1 {
                                        pdl.push(Addr::HeapCell(h1 + 1));
                                    } else {
                                        pdl.push(Addr::EmptyList);
                                    }

                                    if pstr2.at_end(n2 + m_len) {
                                        if has_tail_2 {
                                            pdl.push(Addr::HeapCell(h2 + 1));
                                        } else {
                                            pdl.push(Addr::EmptyList);
                                        }
                                    } else {
                                        pdl.push(Addr::PStrLocation(h2, n2 + m_len));
                                    }
                                } else {
                                    pdl.push(Addr::PStrLocation(h1, n1 + m_len));

                                    if pstr2.at_end(n2 + m_len) {
                                        if has_tail_2 {
                                            pdl.push(Addr::HeapCell(h2 + 1));
                                        } else {
                                            pdl.push(Addr::EmptyList);
                                        }
                                    } else {
                                        pdl.push(Addr::PStrLocation(h2, n2 + m_len));
                                    }
                                }
                            } else {
                                unreachable!()
                            }
                        } else {
                            unreachable!()
                        }
                    }
                    (Addr::Lis(a1), Addr::Lis(a2)) => {
                        pdl.push(Addr::HeapCell(a1));
                        pdl.push(Addr::HeapCell(a2));

                        pdl.push(Addr::HeapCell(a1 + 1));
                        pdl.push(Addr::HeapCell(a2 + 1));
                    }
                    (Addr::Str(a1), Addr::Str(a2)) => {
                        let r1 = &self.heap[a1];
                        let r2 = &self.heap[a2];

                        if let &HeapCellValue::NamedStr(n1, ref f1, _) = r1 {
                            if let &HeapCellValue::NamedStr(n2, ref f2, _) = r2 {
                                if n1 == n2 && *f1 == *f2 {
                                    for i in 1..n1 + 1 {
                                        pdl.push(Addr::HeapCell(a1 + i));
                                        pdl.push(Addr::HeapCell(a2 + i));
                                    }

                                    continue;
                                }
                            }
                        }

                        self.fail = true;
                    }
                    (Addr::Con(c1), Addr::Con(c2)) => {
                        match (&self.heap[c1], &self.heap[c2]) {
                            (
                                &HeapCellValue::Atom(ref n1, _),
                                &HeapCellValue::Atom(ref n2, _),
                            ) if n1.as_str() == n2.as_str() => {
                            }
                            (
                                &HeapCellValue::DBRef(ref db_ref_1),
                                &HeapCellValue::DBRef(ref db_ref_2),
                            ) if db_ref_1 == db_ref_2 => {
                            }
                            (
                                v1,
                                v2,
                            ) => {
                                if let Ok(n1) = Number::try_from(v1) {
                                    if let Ok(n2) = Number::try_from(v2) {
                                        if n1 == n2 {
                                            continue;
                                        }
                                    }
                                }

                                self.fail = true;
                            }
                        }
                    }
                    (Addr::Con(h), Addr::Char(c)) | (Addr::Char(c), Addr::Con(h)) => {
                        match &self.heap[h] {
                            &HeapCellValue::Atom(ref name, _) if name.is_char() => {
                                if name.as_str().chars().next() != Some(c) {
                                    self.fail = true;
                                    return;
                                }
                            }
                            _ => {
                                self.fail = true;
                                return;
                            }
                        }
                    }
                    (Addr::Stream(s1), Addr::Stream(s2)) => {
                        if s1 != s2 {
                            self.fail = true;
                        }
                    }
                    (v, Addr::Con(h)) | (Addr::Con(h), v) => {
                        if let Ok(n1) = Number::try_from(&self.heap[h]) {
                            if let Ok(v) = Number::try_from(&HeapCellValue::Addr(v)) {
                                if n1 == v {
                                    continue;
                                }
                            }
                        }

                        self.fail = true;
                    }
                    (a1, a2) => {
                        if let Ok(n1) = Number::try_from(&HeapCellValue::Addr(a1)) {
                            if let Ok(n2) = Number::try_from(&HeapCellValue::Addr(a2)) {
                                if n1 == n2 {
                                    continue;
                                }
                            }
                        }

                        if a1 != a2 {
                            self.fail = true;
                        }
                    }
                };
            }
        }
    }

    pub(super)
    fn unify(&mut self, a1: Addr, a2: Addr) {
        let mut pdl = vec![a1, a2];

        let mut tabu_list: IndexSet<(Addr, Addr)> = IndexSet::new();

        self.fail = false;

        while !(pdl.is_empty() || self.fail) {
            let d1 = self.deref(pdl.pop().unwrap());
            let d2 = self.deref(pdl.pop().unwrap());

            if d1 != d2 {
                let d1 = self.store(d1);
                let d2 = self.store(d2);

                if tabu_list.contains(&(d1, d2)) {
                    continue;
                } else {
                    tabu_list.insert((d1, d2));
                }

                match (d1, d2) {
                    (Addr::AttrVar(h), addr) | (addr, Addr::AttrVar(h)) => {
                        self.bind(Ref::AttrVar(h), addr);
                    }
                    (Addr::HeapCell(h), addr) | (addr, Addr::HeapCell(h)) => {
                        self.bind(Ref::HeapCell(h), addr);
                    }
                    (Addr::StackCell(fr, sc), addr) | (addr, Addr::StackCell(fr, sc)) => {
                        self.bind(Ref::StackCell(fr, sc), addr);
                    }
                    (Addr::Lis(a1), Addr::Str(a2)) | (Addr::Str(a2), Addr::Lis(a1)) => {
                        if let &HeapCellValue::NamedStr(n2, ref f2, _) = &self.heap[a2] {
                            if f2.as_str() == "." && n2 == 2 {
                                pdl.push(Addr::HeapCell(a1));
                                pdl.push(Addr::HeapCell(a2 + 1));

                                pdl.push(Addr::HeapCell(a1 + 1));
                                pdl.push(Addr::HeapCell(a2 + 2));

                                continue;
                            }
                        }

                        self.fail = true;
                    }
                    (Addr::PStrLocation(h, n), Addr::Lis(l)) |
                    (Addr::Lis(l), Addr::PStrLocation(h, n)) => {
                      if let HeapCellValue::PartialString(ref pstr, _) = &self.heap[h] {
                          if let Some(c) = pstr.range_from(n ..).next() {
                              pdl.push(Addr::PStrLocation(h, n + c.len_utf8()));
                              pdl.push(Addr::HeapCell(l + 1));

                              pdl.push(Addr::Char(c));
                              pdl.push(Addr::HeapCell(l));
                          } else {
                              unreachable!()
                          }
                      } else {
                          unreachable!()
                      }
                    }
                    (Addr::PStrLocation(h1, n1), Addr::PStrLocation(h2, n2)) => {
                        if let &HeapCellValue::PartialString(ref pstr1, has_tail_1) = &self.heap[h1] {
                            if let &HeapCellValue::PartialString(ref pstr2, has_tail_2) = &self.heap[h2] {
                                let pstr1_s = pstr1.as_str_from(n1);
                                let pstr2_s = pstr2.as_str_from(n2);

                                let m_len =
                                    if pstr1_s.starts_with(pstr2_s) {
                                        pstr2_s.len()
                                    } else if pstr2_s.starts_with(pstr1_s) {
                                        pstr1_s.len()
                                    } else {
                                        self.fail = true;
                                        return;
                                    };

                                if pstr1.at_end(n1 + m_len) {
                                    if has_tail_1 {
                                        pdl.push(Addr::HeapCell(h1 + 1));
                                    } else {
                                        pdl.push(Addr::EmptyList);
                                    }

                                    if pstr2.at_end(n2 + m_len) {
                                        if has_tail_2 {
                                            pdl.push(Addr::HeapCell(h2 + 1));
                                        } else {
                                            pdl.push(Addr::EmptyList);
                                        }
                                    } else {
                                        pdl.push(Addr::PStrLocation(h2, n2 + m_len));
                                    }
                                } else {
                                    pdl.push(Addr::PStrLocation(h1, n1 + m_len));

                                    if pstr2.at_end(n2 + m_len) {
                                        if has_tail_2 {
                                            pdl.push(Addr::HeapCell(h2 + 1));
                                        } else {
                                            pdl.push(Addr::EmptyList);
                                        }
                                    } else {
                                        pdl.push(Addr::PStrLocation(h2, n2 + m_len));
                                    }
                                }
                            }
                        }
                    }
                    (Addr::Lis(a1), Addr::Lis(a2)) => {
                        pdl.push(Addr::HeapCell(a1));
                        pdl.push(Addr::HeapCell(a2));

                        pdl.push(Addr::HeapCell(a1 + 1));
                        pdl.push(Addr::HeapCell(a2 + 1));
                    }
                    (Addr::Str(a1), Addr::Str(a2)) => {
                        let r1 = &self.heap[a1];
                        let r2 = &self.heap[a2];

                        if let &HeapCellValue::NamedStr(n1, ref f1, _) = r1 {
                            if let &HeapCellValue::NamedStr(n2, ref f2, _) = r2 {
                                if n1 == n2 && *f1 == *f2 {
                                    for i in 1..n1 + 1 {
                                        pdl.push(Addr::HeapCell(a1 + i));
                                        pdl.push(Addr::HeapCell(a2 + i));
                                    }

                                    continue;
                                }
                            }
                        }

                        self.fail = true;
                    }
                    (Addr::Con(c1), Addr::Con(c2)) => {
                        match (&self.heap[c1], &self.heap[c2]) {
                            (
                                &HeapCellValue::Atom(ref n1, _),
                                &HeapCellValue::Atom(ref n2, _),
                            ) if n1.as_str() == n2.as_str() => {
                            }
(
                                &HeapCellValue::DBRef(ref db_ref_1),
                                &HeapCellValue::DBRef(ref db_ref_2),
                            ) if db_ref_1 == db_ref_2 => {
                            }
                            (
                                v1,
                                v2,
                            ) => {
                                if let Ok(n1) = Number::try_from(v1) {
                                    if let Ok(n2) = Number::try_from(v2) {
                                        if n1 == n2 {
                                            continue;
                                        }
                                    }
                                }

                                self.fail = true;
                            }
                        }
                    }
                    (Addr::Con(h), Addr::Char(c)) | (Addr::Char(c), Addr::Con(h)) => {
                        match &self.heap[h] {
                            &HeapCellValue::Atom(ref name, _) if name.is_char() => {
                                if name.as_str().chars().next() != Some(c) {
                                    self.fail = true;
                                    return;
                                }
                            }
                            _ => {
                                self.fail = true;
                                return;
                            }
                        }
                    }
                    (Addr::Stream(s1), Addr::Stream(s2)) => {
                        if s1 != s2 {
                            self.fail = true;
                        }
                    }
                    (v, Addr::Con(h)) | (Addr::Con(h), v) => {
                        if let Ok(n1) = Number::try_from(&self.heap[h]) {
                            if let Ok(v) = Number::try_from(&HeapCellValue::Addr(v)) {
                                if n1 == v {
                                    continue;
                                }
                            }
                        }

                        self.fail = true;
                    }
                    (a1, a2) => {
                        if let Ok(n1) = Number::try_from(&HeapCellValue::Addr(a1)) {
                            if let Ok(n2) = Number::try_from(&HeapCellValue::Addr(a2)) {
                                if n1 == n2 {
                                    continue;
                                }
                            }
                        }

                        if a1 != a2 {
                            self.fail = true;
                        }
                    }
                };
            }
        }
    }

    pub(super)
    fn trail(&mut self, r: TrailRef) {
        match r {
            TrailRef::Ref(Ref::HeapCell(h)) => {
                if h < self.hb {
                    self.trail.push(TrailRef::Ref(Ref::HeapCell(h)));
                    self.tr += 1;
                }
            }
            TrailRef::Ref(Ref::AttrVar(h)) => {
                if h < self.hb {
                    self.trail.push(TrailRef::Ref(Ref::AttrVar(h)));
                    self.tr += 1;
                }
            }
            TrailRef::AttrVarHeapLink(h) => {
                if h < self.hb {
                    self.trail.push(TrailRef::AttrVarHeapLink(h));
                    self.tr += 1;
                }
            }
            TrailRef::AttrVarListLink(h, l) => {
                if h < self.hb {
                    self.trail.push(TrailRef::AttrVarListLink(h, l));
                    self.tr += 1;
                }
            }
            TrailRef::Ref(Ref::StackCell(b, sc)) => {
                if b < self.b {
                    self.trail.push(TrailRef::Ref(Ref::StackCell(b, sc)));
                    self.tr += 1;
                }
            }
        }
    }

    fn increment_s_ptr(&mut self, rhs: usize) {
        match &mut self.s {
            HeapPtr::HeapCell(ref mut h) => {
                *h += rhs;
            }
            &mut HeapPtr::PStrChar(h, ref mut n) |
            &mut HeapPtr::PStrLocation(h, ref mut n) => {
                match &self.heap[h] {
                    &HeapCellValue::PartialString(ref pstr, _) => {
                        for c in pstr.range_from(*n ..).take(rhs) {
                            *n += c.len_utf8();
                        }

                        self.s = HeapPtr::PStrLocation(h, *n);
                    }
                    _ => {
                    }
                }
            }
        }
    }

    pub(super)
    fn unwind_trail(&mut self, a1: usize, a2: usize) {
        // the sequence is reversed to respect the chronology of trail
        // additions, now that deleted attributes can be undeleted by
        // backtracking.
        for i in (a1..a2).rev() {
            match self.trail[i] {
                TrailRef::Ref(Ref::HeapCell(h)) => {
                    self.heap[h] = HeapCellValue::Addr(Addr::HeapCell(h))
                }
                TrailRef::Ref(Ref::AttrVar(h)) => {
                    self.heap[h] = HeapCellValue::Addr(Addr::AttrVar(h))
                }
                TrailRef::Ref(Ref::StackCell(fr, sc)) => {
                    self.stack.index_and_frame_mut(fr)[sc] = Addr::StackCell(fr, sc)
                }
                TrailRef::AttrVarHeapLink(h) => {
                    self.heap[h] = HeapCellValue::Addr(Addr::HeapCell(h));
                }
                TrailRef::AttrVarListLink(h, l) => {
                    self.heap[h] = HeapCellValue::Addr(Addr::Lis(l));
                }
            }
        }
    }

    pub(super)
    fn tidy_trail(&mut self) {
        if self.b == 0 {
            return;
        }

        let b = self.b;
        let hb = self.hb;
        let mut offset = 0;

        for i in self.stack.index_or_frame(b).prelude.tr .. self.tr {
            match self.trail[i] {
                TrailRef::Ref(Ref::AttrVar(tr_i))
              | TrailRef::Ref(Ref::HeapCell(tr_i))
              | TrailRef::AttrVarHeapLink(tr_i)
              | TrailRef::AttrVarListLink(tr_i, _) => {
                    if tr_i >= hb {
                        offset += 1;
                    } else {
                        self.trail[i - offset] = self.trail[i];
                    }
                }
                TrailRef::Ref(Ref::StackCell(b, _)) => {
                    if b < self.b {
                        self.trail[i - offset] = self.trail[i];
                    } else {
                        offset += 1;
                    }
                }
            }
        }

        self.tr -= offset;
        self.trail.truncate(self.tr);
    }

    pub(super)
    fn match_partial_string(&mut self, addr: Addr, string: &String, has_tail: bool) {
        let mut heap_pstr_iter = self.heap_pstr_iter(addr);

        match compare_pstr_to_string(&mut heap_pstr_iter, string) {
            Some(prefix_len) if prefix_len == string.len() => {
                let focus = heap_pstr_iter.focus();

                match focus {
                    Addr::PStrLocation(h, n) => {
                        if has_tail {
                            self.s = HeapPtr::PStrLocation(h, n);
                            self.mode = MachineMode::Read;
                        } else {
                            self.fail = true;
                        }
                    }
                    addr => {
                        if has_tail {
                            let h = self.heap.h();

                            self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));
                            self.bind(Ref::HeapCell(h), addr);

                            self.s = HeapPtr::HeapCell(h);
                            self.mode = MachineMode::Read;
                        } else {
                            if let Some(var) = addr.as_var() {
                                self.bind(var, Addr::EmptyList);
                            } else {
                                self.fail = addr != Addr::EmptyList;
                            }
                        }
                    }
                }
            }
            Some(prefix_len) => {
                match heap_pstr_iter.focus() {
                    addr if addr.is_ref() => {
                        let h = self.heap.h();

                        let pstr_addr =
                            if has_tail {
                                self.s = HeapPtr::HeapCell(h+1);
                                self.mode = MachineMode::Read;

                                self.heap.allocate_pstr(&string[prefix_len ..])
                            } else {
                                self.heap.put_complete_string(&string[prefix_len ..])
                            };

                        self.bind(addr.as_var().unwrap(), pstr_addr);
                    }
                    Addr::Lis(l) => {
                        let h = self.heap.h();

                        let pstr_addr =
                            if has_tail {
                                self.s = HeapPtr::HeapCell(h+1);
                                self.mode = MachineMode::Read;

                                self.heap.allocate_pstr(&string[prefix_len ..])
                            } else {
                                self.heap.put_complete_string(&string[prefix_len ..])
                            };

                        self.unify(Addr::Lis(l), pstr_addr);
                    }
                    _ => {
                        self.fail = true;
                    }
                }
            }
            None => {
                self.fail = true;
            }
        }
    }

    pub(super)
    fn write_constant_to_var(&mut self, addr: Addr, c: &Constant) {
        match self.store(self.deref(addr)) {
            Addr::Con(c1) => {
                match &self.heap[c1] {
                    HeapCellValue::Atom(ref n1, _) => {
                        self.fail = match c {
                            Constant::Atom(ref n2, _) => {
                                n1 != n2
                            }
                            Constant::Char(c) if n1.is_char() => {
                                Some(*c) != n1.as_str().chars().next()
                            }
                            _ => {
                                true
                            }
                        };
                    }
                    HeapCellValue::Integer(ref n1) => {
                        self.fail = match c {
                            Constant::Fixnum(n2) => {
                                n1.to_isize() != Some(*n2)
                            }
                            Constant::Integer(ref n2) => {
                                n1 != n2
                            }
                            Constant::Usize(n2) => {
                                n1.to_usize() != Some(*n2)
                            }
                            _ => {
                                true
                            }
                        };
                    }
                    HeapCellValue::Rational(ref r1) => {
                        self.fail = if let Constant::Rational(ref r2) = c {
                            r1 != r2
                        } else {
                            true
                        }
                    }
                    HeapCellValue::PartialString(..) => {
                        if let Constant::String(ref s2) = c {
                            self.match_partial_string(
                                Addr::PStrLocation(c1, 0),
                                &s2,
                                false,
                            );
                        } else {
                            self.fail = true;
                        }
                    }
                    _ => {
                        unreachable!()
                    }
                };
            }
            Addr::Char(ch) => {
                self.fail = match c {
                    Constant::Atom(ref n2, _) if n2.is_char() => {
                        Some(ch) != n2.as_str().chars().next()
                    }
                    Constant::Char(c) => {
                        *c != ch
                    }
                    _ => {
                        true
                    }
                };
            }
            Addr::EmptyList => {
                if let Constant::EmptyList = c {
                } else {
                    self.fail = true;
                }
            }
            Addr::Lis(l) => {
                let addr = self.heap.put_constant(c.clone());
                self.unify(Addr::Lis(l), addr);
            }
            Addr::PStrLocation(h, n) => {
                if let Constant::String(ref s2) = c {
                    self.match_partial_string(
                        Addr::PStrLocation(h, n),
                        &s2,
                        false,
                    )
                } else {
                    self.fail = true;
                };
            }
            Addr::Stream(_) => {
                self.fail = true;
            }
            addr => {
                let c = self.heap.put_constant(c.clone());

                if let Some(r) = addr.as_var() {
                    self.bind(r, c);
                } else {
                    self.unify(addr, c);
                }
            }
        };
    }

    pub(super)
    fn execute_arith_instr(&mut self, instr: &ArithmeticInstruction) {
        let stub = MachineError::functor_stub(clause_name!("is"), 2);

        match instr {
            &ArithmeticInstruction::Add(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, try_numeric_result!(self, n1 + n2, stub));
                self.p += 1;
            }
            &ArithmeticInstruction::Sub(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, try_numeric_result!(self, n1 - n2, stub));
                self.p += 1;
            }
            &ArithmeticInstruction::Mul(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, try_numeric_result!(self, n1 * n2, stub));
                self.p += 1;
            }
            &ArithmeticInstruction::Max(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.max(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Min(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.min(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::IntPow(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.int_pow(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Gcd(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.gcd(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Pow(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.pow(n1, n2, "(**)"));
                self.p += 1;
            }
            &ArithmeticInstruction::RDiv(ref a1, ref a2, t) => {
                let stub = MachineError::functor_stub(clause_name!("(rdiv)"), 2);

                let (r1, stub) = try_or_fail!(self, self.get_rational(a1, stub));
                let (r2, _)    = try_or_fail!(self, self.get_rational(a2, stub));

                self.interms[t - 1] =
                    Number::Rational(Rc::new(try_or_fail!(self, self.rdiv(r1, r2))));
                self.p += 1;
            }
            &ArithmeticInstruction::IntFloorDiv(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.int_floor_div(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::IDiv(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.idiv(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Abs(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = n1.abs();
                self.p += 1;
            }
            &ArithmeticInstruction::Sign(ref a1, t) => {
                let n = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = self.sign(n);
                self.p += 1;
            }
            &ArithmeticInstruction::Neg(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = -n1;
                self.p += 1;
            }
            &ArithmeticInstruction::BitwiseComplement(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = try_or_fail!(self, self.bitwise_complement(n1));
                self.p += 1;
            }
            &ArithmeticInstruction::Div(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.div(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Shr(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.shr(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Shl(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.shl(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Xor(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.xor(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::And(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.and(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Or(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.or(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Mod(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.modulus(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Rem(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] = try_or_fail!(self, self.remainder(n1, n2));
                self.p += 1;
            }
            &ArithmeticInstruction::Cos(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = Number::Float(OrderedFloat(try_or_fail!(self, self.cos(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::Sin(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = Number::Float(OrderedFloat(try_or_fail!(self, self.sin(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::Tan(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = Number::Float(OrderedFloat(try_or_fail!(self, self.tan(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::Sqrt(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] =
                    Number::Float(OrderedFloat(try_or_fail!(self, self.sqrt(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::Log(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = Number::Float(OrderedFloat(try_or_fail!(self, self.log(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::Exp(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = Number::Float(OrderedFloat(try_or_fail!(self, self.exp(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::ACos(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] =
                    Number::Float(OrderedFloat(try_or_fail!(self, self.acos(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::ASin(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] =
                    Number::Float(OrderedFloat(try_or_fail!(self, self.asin(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::ATan(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] =
                    Number::Float(OrderedFloat(try_or_fail!(self, self.atan(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::ATan2(ref a1, ref a2, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));
                let n2 = try_or_fail!(self, self.get_number(a2));

                self.interms[t - 1] =
                    Number::Float(OrderedFloat(try_or_fail!(self, self.atan2(n1, n2))));
                self.p += 1;
            }
            &ArithmeticInstruction::Float(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] =
                    Number::Float(OrderedFloat(try_or_fail!(self, self.float(n1))));
                self.p += 1;
            }
            &ArithmeticInstruction::Truncate(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = self.truncate(n1);
                self.p += 1;
            }
            &ArithmeticInstruction::Round(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = try_or_fail!(self, self.round(n1));
                self.p += 1;
            }
            &ArithmeticInstruction::Ceiling(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = self.ceiling(n1);
                self.p += 1;
            }
            &ArithmeticInstruction::Floor(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = self.floor(n1);
                self.p += 1;
            }
            &ArithmeticInstruction::Plus(ref a1, t) => {
                let n1 = try_or_fail!(self, self.get_number(a1));

                self.interms[t - 1] = n1;
                self.p += 1;
            }
        };
    }

    pub(super)
    fn execute_fact_instr(&mut self, instr: &FactInstruction) {
        match instr {
            &FactInstruction::GetConstant(_, ref c, reg) => {
                let addr = self[reg];
                self.write_constant_to_var(addr, c);
            }
            &FactInstruction::GetList(_, reg) => {
                let addr = self.store(self.deref(self[reg]));

                match addr {
                    Addr::PStrLocation(h, n) => {
                        self.s = HeapPtr::PStrChar(h, n);
                        self.mode = MachineMode::Read;
                    }
                    addr @ Addr::AttrVar(_) |
                    addr @ Addr::StackCell(..) |
                    addr @ Addr::HeapCell(_) => {
                        let h = self.heap.h();

                        self.heap.push(HeapCellValue::Addr(Addr::Lis(h + 1)));
                        self.bind(addr.as_var().unwrap(), Addr::HeapCell(h));

                        self.mode = MachineMode::Write;
                    }
                    Addr::Lis(a) => {
                        self.s = HeapPtr::HeapCell(a);
                        self.mode = MachineMode::Read;
                    }
                    _ => {
                        self.fail = true;
                    }
                };
            }
            &FactInstruction::GetPartialString(_, ref string, reg, has_tail) => {
                let addr = self.store(self.deref(self[reg]));
                self.match_partial_string(addr, string, has_tail);
            }
            &FactInstruction::GetStructure(ref ct, arity, reg) => {
                let addr = self.deref(self[reg]);

                match self.store(addr) {
                    Addr::Str(a) => {
                        let result = &self.heap[a];

                        if let &HeapCellValue::NamedStr(narity, ref s, _) = result {
                            if narity == arity && ct.name() == *s {
                                self.s = HeapPtr::HeapCell(a + 1);
                                self.mode = MachineMode::Read;
                            } else {
                                self.fail = true;
                            }
                        }
                    }
                    Addr::AttrVar(_) | Addr::HeapCell(_) | Addr::StackCell(_, _) => {
                        let h = self.heap.h();

                        self.heap.push(HeapCellValue::Addr(Addr::Str(h + 1)));
                        self.heap.push(HeapCellValue::NamedStr(arity, ct.name(), ct.spec()));

                        self.bind(addr.as_var().unwrap(), Addr::HeapCell(h));

                        self.mode = MachineMode::Write;
                    }
                    _ => {
                        self.fail = true;
                    }
                };
            }
            &FactInstruction::GetVariable(norm, arg) => {
                self[norm] = self.registers[arg];
            }
            &FactInstruction::GetValue(norm, arg) => {
                let norm_addr = self[norm];
                let reg_addr = self.registers[arg];

                self.unify(norm_addr, reg_addr);
            }
            &FactInstruction::UnifyConstant(ref c) => {
                match self.mode {
                    MachineMode::Read => {
                        let addr = self.s.read(&self.heap);

                        self.write_constant_to_var(addr, c);
                        self.increment_s_ptr(1);
                    }
                    MachineMode::Write => {
                        let addr = self.heap.put_constant(c.clone());

                        if !addr.is_heap_bound() {
                            self.heap.push(HeapCellValue::Addr(addr));
                        }
                    }
                };
            }
            &FactInstruction::UnifyVariable(reg) => {
                match self.mode {
                    MachineMode::Read => {
                        self[reg] = self.s.read(&self.heap);
                        self.increment_s_ptr(1);
                    }
                    MachineMode::Write => {
                        let h = self.heap.h();

                        self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));
                        self[reg] = Addr::HeapCell(h);
                    }
                };
            }
            &FactInstruction::UnifyLocalValue(reg) => {
                match self.mode {
                    MachineMode::Read => {
                        let reg_addr = self[reg];

                        self.unify(reg_addr, self.s.read(&self.heap));
                        self.increment_s_ptr(1);
                    }
                    MachineMode::Write => {
                        let addr = self.deref(self[reg]);
                        let h = self.heap.h();

                        if let Addr::HeapCell(hc) = addr {
                            if hc < h {
                                let val = self.heap.clone(hc);

                                self.heap.push(val);
                                self.increment_s_ptr(1);

                                return;
                            }
                        }

                        self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));
                        self.bind(Ref::HeapCell(h), addr);
                    }
                };
            }
            &FactInstruction::UnifyValue(reg) => {
                match self.mode {
                    MachineMode::Read => {
                        let reg_addr = self[reg];

                        self.unify(reg_addr, self.s.read(&self.heap));
                        self.increment_s_ptr(1);
                    }
                    MachineMode::Write => {
                        let heap_val = self.store(self[reg]);
                        self.heap.push(HeapCellValue::Addr(heap_val));
                    }
                };
            }
            &FactInstruction::UnifyVoid(n) => {
                match self.mode {
                    MachineMode::Read => {
                        self.increment_s_ptr(n);
                    }
                    MachineMode::Write => {
                        let h = self.heap.h();

                        for i in h..h + n {
                            self.heap.push(HeapCellValue::Addr(Addr::HeapCell(i)));
                        }
                    }
                };
            }
        };
    }

    pub(super)
    fn execute_indexing_instr(&mut self, instr: &IndexingInstruction) {
        match instr {
            &IndexingInstruction::SwitchOnTerm(v, c, l, s) => {
                let addr = self[temp_v!(1)];
                let addr = self.store(self.deref(addr));

                let offset = match addr {
                    Addr::Stream(_) | Addr::TcpListener(_) => {
                        0
                    }
                    Addr::HeapCell(_) | Addr::StackCell(..) | Addr::AttrVar(..) => {
                        v
                    }
                    Addr::PStrLocation(..) => {
                        l
                    }
                    Addr::Char(_) | Addr::Con(_) | Addr::CutPoint(_) |
                    Addr::EmptyList | Addr::Fixnum(_) | Addr::Float(_) | Addr::Usize(_) => {
                        c
                    }
                    Addr::Lis(_) => {
                        l
                    }
                    Addr::Str(_) => {
                        s
                    }
                };

                match offset {
                    0 => self.fail = true,
                    o => self.p += o,
                };
            }
            &IndexingInstruction::SwitchOnConstant(_, ref hm) => {
                let addr = self[temp_v!(1)];
                let addr = self.store(self.deref(addr));

                let offset =
                    match addr.as_constant_index(&self) {
                        Some(c) => {
                            match hm.get(&c) {
                                Some(offset) => *offset,
                                _ => 0,
                            }
                        }
                        None => {
                            0
                        }
                    };

                match offset {
                    0 => self.fail = true,
                    o => self.p += o,
                };
            }
            &IndexingInstruction::SwitchOnStructure(_, ref hm) => {
                let a1 = self.registers[1];
                let addr = self.store(self.deref(a1));

                let offset = match addr {
                    Addr::Str(s) => {
                        if let &HeapCellValue::NamedStr(arity, ref name, _) = &self.heap[s] {
                            match hm.get(&(name.clone(), arity)) {
                                Some(offset) => *offset,
                                _ => 0,
                            }
                        } else {
                            0
                        }
                    }
                    _ => {
                        0
                    }
                };

                match offset {
                    0 => self.fail = true,
                    o => self.p += o,
                };
            }
        };
    }

    pub(super) fn execute_query_instr(&mut self, instr: &QueryInstruction) {
        match instr {
            &QueryInstruction::GetVariable(norm, arg) => {
                self[norm] = self.registers[arg];
            }
            &QueryInstruction::PutConstant(_, ref c, reg) => {
                self[reg] = self.heap.put_constant(c.clone());
            }
            &QueryInstruction::PutList(_, reg) => {
                self[reg] = Addr::Lis(self.heap.h());
            }
            &QueryInstruction::PutPartialString(_, ref string, reg, has_tail) => {
                let pstr_addr =
                    if has_tail {
                        if !string.is_empty() {
                            let pstr_addr = self.heap.allocate_pstr(&string);
                            self.heap.pop(); // the tail will be added by the next instruction.
                            pstr_addr
                        } else {
                            Addr::EmptyList
                        }
                    } else {
                        self.heap.put_complete_string(&string)
                    };

                self[reg] = pstr_addr;
            }
            &QueryInstruction::PutStructure(ref ct, arity, reg) => {
                let h = self.heap.h();

                self.heap.push(HeapCellValue::NamedStr(arity, ct.name(), ct.spec()));
                self[reg] = Addr::Str(h);
            }
            &QueryInstruction::PutUnsafeValue(n, arg) => {
                let e = self.e;
                let addr = self.store(self.deref(Addr::StackCell(e, n)));

                if addr.is_protected(e) {
                    self.registers[arg] = addr;
                } else {
                    let h = self.heap.h();

                    self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));
                    self.bind(Ref::HeapCell(h), addr);

                    self.registers[arg] = self.heap[h].as_addr(h);
                }
            }
            &QueryInstruction::PutValue(norm, arg) => {
                self.registers[arg] = self[norm];
            }
            &QueryInstruction::PutVariable(norm, arg) => {
                match norm {
                    RegType::Perm(n) => {
                        let e = self.e;

                        self[norm] = Addr::StackCell(e, n);
                        self.registers[arg] = self[norm];
                    }
                    RegType::Temp(_) => {
                        let h = self.heap.h();
                        self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));

                        self[norm] = Addr::HeapCell(h);
                        self.registers[arg] = Addr::HeapCell(h);
                    }
                };
            }
            &QueryInstruction::SetConstant(ref c) => {
                let addr = self.heap.put_constant(c.clone());

                if !addr.is_heap_bound() {
                    self.heap.push(HeapCellValue::Addr(addr));
                }
            }
            &QueryInstruction::SetLocalValue(reg) => {
                let addr = self.deref(self[reg]);
                let h = self.heap.h();

                if addr < Ref::HeapCell(h) {
                    self.heap.push(HeapCellValue::Addr(addr));
                    return;
                }

                self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));
                self.bind(Ref::HeapCell(h), addr);
            }
            &QueryInstruction::SetVariable(reg) => {
                let h = self.heap.h();
                self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h)));
                self[reg] = Addr::HeapCell(h);
            }
            &QueryInstruction::SetValue(reg) => {
                let heap_val = self.store(self[reg]);
                self.heap.push(HeapCellValue::Addr(heap_val));
            }
            &QueryInstruction::SetVoid(n) => {
                let h = self.heap.h();

                for i in h .. h + n {
                    self.heap.push(HeapCellValue::Addr(Addr::HeapCell(i)));
                }
            }
        }
    }

    pub(super) fn set_ball(&mut self) {
        self.ball.reset();

        let addr = self[temp_v!(1)];
        self.ball.boundary = self.heap.h();

        copy_term(
            CopyBallTerm::new(&mut self.stack, &mut self.heap, &mut self.ball.stub),
            addr,
            AttrVarPolicy::DeepCopy,
        );
    }

    pub(super)
    fn handle_internal_call_n(&mut self, arity: usize) {
        let arity = arity + 1;
        let pred = self.registers[1];

        for i in 2..arity {
            self.registers[i - 1] = self.registers[i];
        }

        if arity > 1 {
            self.registers[arity - 1] = pred;
            return;
        }

        self.fail = true;
    }

    pub(super)
    fn setup_call_n(&mut self, arity: usize) -> Option<PredicateKey> {
        let addr = self.store(self.deref(self.registers[arity]));

        let (name, narity) = match addr {
            Addr::Str(a) => {
                let result = self.heap.clone(a);

                if let HeapCellValue::NamedStr(narity, name, _) = result {
                    let stub = MachineError::functor_stub(clause_name!("call"), arity + 1);

                    if narity + arity > MAX_ARITY {
                        let representation_error = self.error_form(
                            MachineError::representation_error(RepFlag::MaxArity),
                            stub,
                        );

                        self.throw_exception(representation_error);
                        return None;
                    }

                    for i in (1 .. arity).rev() {
                        self.registers[i + narity] = self.registers[i];
                    }

                    for i in 1 .. narity + 1 {
                        self.registers[i] = self.heap[a + i].as_addr(a + i);
                    }

                    (name, narity)
                } else {
                    self.fail = true;
                    return None;
                }
            }
            Addr::Con(h) =>
                match &self.heap[h] {
                    HeapCellValue::Atom(ref name, _) => {
                        (name.clone(), 0)
                    }
                    _ => {
                        self.fail = true;
                        return None;
                    }
                }
            Addr::HeapCell(_) | Addr::StackCell(_, _) => {
                let stub = MachineError::functor_stub(clause_name!("call"), arity + 1);
                let instantiation_error = self.error_form(
                    MachineError::instantiation_error(),
                    stub,
                );

                self.throw_exception(instantiation_error);
                return None;
            }
            _ => {
                let stub = MachineError::functor_stub(clause_name!("call"), arity + 1);
                let type_error = self.error_form(
                    MachineError::type_error(self.heap.h(), ValidType::Callable, addr),
                    stub,
                );

                self.throw_exception(type_error);
                return None;
            }
        };

        Some((name, arity + narity - 1))
    }

    pub(super) fn unwind_stack(&mut self) {
        self.b = self.block;
        self.fail = true;
    }

    pub(crate) fn is_cyclic_term(&self, addr: Addr) -> bool {
        let mut seen = IndexSet::new();
        let mut fail = false;
        let mut iter = self.pre_order_iter(addr);

        loop {
            if let Some(addr) = iter.stack().last() {
                if !seen.contains(addr) {
                    seen.insert(*addr);
                } else {
                    fail = true;
                    break;
                }
            }

            if iter.next().is_none() {
                break;
            }
        }

        fail
    }

    // arg(+N, +Term, ?Arg)
    pub(super)
    fn try_arg(&mut self) -> CallResult {
        let stub = MachineError::functor_stub(clause_name!("arg"), 3);
        let n = self.store(self.deref(self[temp_v!(1)]));

        match n {
            Addr::HeapCell(_) | Addr::StackCell(..) => { // 8.5.2.3 a)
                return Err(self.error_form(MachineError::instantiation_error(), stub))
            }
            addr => {
                let n =
                    match Number::try_from((addr, &self.heap)) {
                        Ok(Number::Fixnum(n))  => Integer::from(n),
                        Ok(Number::Integer(n)) => Integer::from(n.as_ref()),
                        _ => {
                            return Err(self.error_form(
                                MachineError::type_error(
                                    self.heap.h(),
                                    ValidType::Integer,
                                    addr,
                                ),
                                stub,
                            ));
                        }
                    };

                if n < 0 { // 8.5.2.3 e)
                    let n = Number::from(n);
                    let dom_err = MachineError::domain_error(
                        DomainErrorType::NotLessThanZero,
                        n,
                    );

                    return Err(self.error_form(dom_err, stub));
                }

                let n =
                    match n.to_usize() {
                        Some(n) => n,
                        None => {
                            self.fail = true;
                            return Ok(());
                        }
                    };

                let term = self.store(self.deref(self[temp_v!(2)]));

                match term {
                    Addr::HeapCell(_) | Addr::StackCell(..) | Addr::AttrVar(_) => { // 8.5.2.3 b)
                        return Err(self.error_form(MachineError::instantiation_error(), stub))
                    }
                    Addr::Str(o) => match self.heap.clone(o) {
                        HeapCellValue::NamedStr(arity, _, _) if 1 <= n && n <= arity => {
                            let a3 = self[temp_v!(3)];
                            let h_a = Addr::HeapCell(o + n);

                            self.unify(a3, h_a);
                        }
                        _ => {
                            self.fail = true;
                        }
                    },
                    Addr::Lis(l) => {
                        if n == 1 || n == 2 {
                            let a3 = self[temp_v!(3)];
                            let h_a = Addr::HeapCell(l + n - 1);

                            self.unify(a3, h_a);
                        } else {
                            self.fail = true;
                        }
                    }
                    Addr::PStrLocation(h, offset) => {
                        if n == 1 || n == 2 {
                            let a3 = self[temp_v!(3)];
                            let h_a =
                                if let HeapCellValue::PartialString(ref pstr, _) = &self.heap[h] {
                                    if let Some(c) = pstr.range_from(offset ..).next() {
                                        if n == 1 {
                                            Addr::Char(c)
                                        } else {
                                            Addr::PStrLocation(h, offset + c.len_utf8())
                                        }
                                    } else {
                                        unreachable!()
                                    }
                                } else {
                                    unreachable!()
                                };

                            self.unify(a3, h_a);
                        } else {
                            self.fail = true;
                        }
                    }
                    _ => { // 8.5.2.3 d)
                        return Err(self.error_form(
                            MachineError::type_error(
                                self.heap.h(),
                                ValidType::Compound,
                                term,
                            ),
                            stub,
                        ))
                    }
                }
            }
        }

        Ok(())
    }

    fn compare_numbers(&mut self, cmp: CompareNumberQT, n1: Number, n2: Number) {
        let ordering = n1.cmp(&n2);

        self.fail = match cmp {
            CompareNumberQT::GreaterThan if ordering == Ordering::Greater => false,
            CompareNumberQT::GreaterThanOrEqual if ordering != Ordering::Less => false,
            CompareNumberQT::LessThan if ordering == Ordering::Less => false,
            CompareNumberQT::LessThanOrEqual if ordering != Ordering::Greater => false,
            CompareNumberQT::NotEqual if ordering != Ordering::Equal => false,
            CompareNumberQT::Equal if ordering == Ordering::Equal => false,
            _ => true,
        };

        self.p += 1;
    }

    pub(super)
    fn compare_term(&mut self, qt: CompareTermQT) {
        let a1 = self[temp_v!(1)];
        let a2 = self[temp_v!(2)];

        match self.compare_term_test(&a1, &a2) {
            Some(Ordering::Greater) => match qt {
                CompareTermQT::GreaterThan | CompareTermQT::GreaterThanOrEqual => return,
                _ => self.fail = true,
            },
            Some(Ordering::Equal) => match qt {
                CompareTermQT::GreaterThanOrEqual | CompareTermQT::LessThanOrEqual => return,
                _ => self.fail = true,
            },
            Some(Ordering::Less) => match qt {
                CompareTermQT::LessThan | CompareTermQT::LessThanOrEqual => return,
                _ => self.fail = true,
            },
            None => {
                self.fail = true;
            }
        };
    }

    // returns true on failure.
    pub(super)
    fn eq_test(&self, a1: Addr, a2: Addr) -> bool {
        let mut iter = self.zipped_acyclic_pre_order_iter(a1, a2);

        while let Some((v1, v2)) = iter.next() {
            match (v1, v2) {
                (Addr::Str(s1), Addr::Str(s2)) => {
                    if let HeapCellValue::NamedStr(ar1, n1, _) = &self.heap[s1] {
                        if let HeapCellValue::NamedStr(ar2, n2, _) = &self.heap[s2] {
                            if ar1 != ar2 || n1 != n2 {
                                return true;
                            }
                        } else {
                            unreachable!()
                        }
                    } else {
                        unreachable!()
                    }
                }
                (Addr::PStrLocation(..), Addr::Lis(_)) |
                (Addr::Lis(_), Addr::PStrLocation(..)) => {
                    continue;
                }
                (pstr1 @ Addr::PStrLocation(..), pstr2 @ Addr::PStrLocation(..)) => {
                    let mut i1 = self.heap_pstr_iter(pstr1);
                    let mut i2 = self.heap_pstr_iter(pstr2);

                    let ordering = compare_pstr_prefixes(&mut i1, &mut i2);

                    if let Some(ordering) = ordering {
                        if ordering != Ordering::Equal {
                            return true;
                        }
                    }

                    let (lstack, rstack) = iter.stack();

                    lstack.pop();
                    lstack.pop();

                    rstack.pop();
                    rstack.pop();

                    lstack.push(i1.focus());
                    rstack.push(i2.focus());
                }
                (Addr::Lis(_), Addr::Lis(_)) => {
                    continue;
                }
                (Addr::Con(h1), Addr::Con(h2)) => {
                    match (&self.heap[h1], &self.heap[h2]) {
                        (
                            &HeapCellValue::Atom(ref n1, ref spec_1),
                            &HeapCellValue::Atom(ref n2, ref spec_2),
                        ) => {
                            if n1 != n2 || spec_1 != spec_2 {
                                return true;
                            }
                        }
                        (
                            &HeapCellValue::DBRef(ref db_ref_1),
                            &HeapCellValue::DBRef(ref db_ref_2),
                        ) => {
                            if db_ref_1 != db_ref_2 {
                                return true;
                            }
                        }
                        (
                            v1,
                            v2,
                        ) => {
                            if let Ok(n1) = Number::try_from(v1) {
                                if let Ok(n2) = Number::try_from(v2) {
                                    if n1 == n2 {
                                        continue;
                                    }
                                }
                            }

                            return true;
                        }
                    }
                }
                (Addr::Con(h), Addr::Char(c)) | (Addr::Char(c), Addr::Con(h)) => {
                    match &self.heap[h] {
                        &HeapCellValue::Atom(ref name, _) if name.is_char() => {
                            if name.as_str().chars().next() != Some(c) {
                                return true;
                            }
                        }
                        _ => {
                            return true;
                        }
                    }
                }
                (a1, a2) => {
                    if let Ok(n1) = Number::try_from((a1, &self.heap)) {
                        if let Ok(n2) = Number::try_from((a2, &self.heap)) {
                            if n1 != n2 {
                                return true;
                            } else {
                                continue;
                            }
                        }
                    }

                    if a1 != a2 {
                        return true;
                    }
                }
            }
        }

        // did the two iterators expire at the same step?
        iter.first_to_expire != Ordering::Equal
    }

    pub(super)
    fn compare_term_test(&self, a1: &Addr, a2: &Addr) -> Option<Ordering> {
        let mut iter = self.zipped_acyclic_pre_order_iter(*a1, *a2);

        while let Some((v1, v2)) = iter.next() {
            let order_cat_v1 = v1.order_category(&self.heap);
            let order_cat_v2 = v2.order_category(&self.heap);

            if order_cat_v1 != order_cat_v2 {
                return Some(order_cat_v1.cmp(&order_cat_v2));
            }

            match order_cat_v1 {
                Some(TermOrderCategory::Variable) => {
                    let v1 = v1.as_var().unwrap();
                    let v2 = v2.as_var().unwrap();

                    if v1 != v2 {
                        return Some(v1.cmp(&v2));
                    }
                }
                Some(TermOrderCategory::FloatingPoint) => {
                    if let Addr::Float(f1) = v1 {
                        if let Addr::Float(f2) = v2 {
                            return Some(f1.cmp(&f2));
                        } else {
                            unreachable!()
                        }
                    } else {
                        unreachable!()
                    }
                }
                Some(TermOrderCategory::Integer) => {
                    match (v1, v2) {
                        (
                            Addr::Con(h1),
                            Addr::Con(h2),
                        ) => {
                            if let Ok(n1) = Number::try_from(&self.heap[h1]) {
                                if let Ok(n2) = Number::try_from(&self.heap[h2]) {
                                    if n1 != n2 {
                                        return Some(n1.cmp(&n2));
                                    }
                                } else {
                                    unreachable!()
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Con(h1),
                            v2,
                        ) => {
                            if let Ok(n1) = Number::try_from(&self.heap[h1]) {
                                if let Ok(n2) = Number::try_from(&HeapCellValue::Addr(v2)) {
                                    if n1 != n2 {
                                        return Some(n1.cmp(&n2));
                                    }
                                } else {
                                    unreachable!()
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            v1,
                            Addr::Con(h2),
                        ) => {
                            if let Ok(n1) = Number::try_from(&HeapCellValue::Addr(v1)) {
                                if let Ok(n2) = Number::try_from(&self.heap[h2]) {
                                    if n1 != n2 {
                                        return Some(n1.cmp(&n2));
                                    }
                                } else {
                                    unreachable!()
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (v1, v2) => {
                            if let Ok(n1) = Number::try_from(&HeapCellValue::Addr(v1)) {
                                if let Ok(n2) = Number::try_from(&HeapCellValue::Addr(v2)) {
                                    if n1 != n2 {
                                        return Some(n1.cmp(&n2));
                                    }
                                } else {
                                    unreachable!()
                                }
                            } else {
                                unreachable!()
                            }
                        }
                    }
                }
                Some(TermOrderCategory::Atom) => {
                    match (v1, v2) {
                        (
                            Addr::Con(h1),
                            Addr::Con(h2),
                        ) => {
                            if let HeapCellValue::Atom(ref n1, _) = &self.heap[h1] {
                                if let HeapCellValue::Atom(ref n2, _) = &self.heap[h2] {
                                    if n1 != n2 {
                                        return Some(n1.cmp(&n2));
                                    }
                                } else {
                                    unreachable!()
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Con(h1),
                            Addr::Char(c),
                        ) => {
                            if let HeapCellValue::Atom(ref n1, _) = &self.heap[h1] {
                                if n1.is_char() {
                                    if n1.as_str().chars().next() != Some(c) {
                                        return Some(n1.as_str().chars().next().cmp(&Some(c)));
                                    }
                                } else {
                                    return Some(Ordering::Greater);
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Char(c),
                            Addr::Con(h1),
                        ) => {
                            if let HeapCellValue::Atom(ref n1, _) = &self.heap[h1] {
                                if n1.is_char() {
                                    if n1.as_str().chars().next() != Some(c) {
                                        return Some(Some(c).cmp(&n1.as_str().chars().next()));
                                    }
                                } else {
                                    return Some(Ordering::Less);
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::EmptyList,
                            Addr::Con(h),
                        ) => {
                            if let HeapCellValue::Atom(ref n1, _) = &self.heap[h] {
                                if "[]" != n1.as_str() {
                                    return Some("[]".cmp(n1.as_str()));
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Con(h),
                            Addr::EmptyList,
                        ) => {
                            if let HeapCellValue::Atom(ref n1, _) = &self.heap[h] {
                                if "[]" != n1.as_str() {
                                    return Some(n1.as_str().cmp("[]"));
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Char(c1),
                            Addr::Char(c2),
                        ) => {
                            if c1 != c2 {
                                return Some(c1.cmp(&c2));
                            }
                        }
                        (
                            Addr::Char(c),
                            Addr::EmptyList,
                        ) => {
                            return if c == '[' {
                                Some(Ordering::Less)
                            } else {
                                Some(c.cmp(&'['))
                            };
                        }
                        (
                            Addr::EmptyList,
                            Addr::Char(c),
                        ) => {
                            return if c == '[' {
                                Some(Ordering::Greater)
                            } else {
                                Some('['.cmp(&c))
                            };
                        }
                        (
                            Addr::EmptyList,
                            Addr::EmptyList,
                        ) => {
                        }
                        _ => {
                            return None;
                        }
                    }
                }
                Some(TermOrderCategory::Compound) => {
                    match (v1, v2) {
                        (
                            Addr::Lis(_),
                            Addr::Lis(_),
                        ) => {
                        }
                        (
                            pstr1 @ Addr::PStrLocation(..),
                            pstr2 @ Addr::PStrLocation(..),
                        ) => {
                            let mut i1 = self.heap_pstr_iter(pstr1);
                            let mut i2 = self.heap_pstr_iter(pstr2);

                            let ordering = compare_pstr_prefixes(&mut i1, &mut i2);

                            if let Some(ordering) = ordering {
                                if ordering != Ordering::Equal {
                                    return Some(ordering);
                                }
                            } else {
                                let (lstack, rstack) = iter.stack();

                                lstack.pop();
                                lstack.pop();

                                rstack.pop();
                                rstack.pop();

                                lstack.push(i1.focus());
                                rstack.push(i2.focus());
                            }
                        }
                        (
                            Addr::Str(h1),
                            Addr::Str(h2),
                        ) => {
                            if let HeapCellValue::NamedStr(a1, ref n1, _) = &self.heap[h1] {
                                if let HeapCellValue::NamedStr(a2, ref n2, _) = &self.heap[h2] {
                                    if a1 != a2 || n1.as_str() != n2.as_str() {
                                        return Some(a1.cmp(&a2).then_with(|| n1.as_str().cmp(n2.as_str())));
                                    }
                                } else {
                                    unreachable!()
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Lis(_),
                            Addr::PStrLocation(..),
                        ) |
                        (
                            Addr::PStrLocation(..),
                            Addr::Lis(_),
                        ) => {
                        }
                        (
                            Addr::Lis(_),
                            Addr::Str(s),
                        ) => {
                            if let &HeapCellValue::NamedStr(a1, ref n1, _) = &self.heap[s] {
                                if a1 != 2 || n1.as_str() != "." {
                                    return Some(a1.cmp(&2).then_with(|| n1.as_str().cmp(".")));
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Str(s),
                            Addr::Lis(_),
                        ) => {
                            if let &HeapCellValue::NamedStr(a1, ref n1, _) = &self.heap[s] {
                                if a1 != 2 || n1.as_str() != "." {
                                    return Some(2.cmp(&a1).then_with(|| ".".cmp(n1.as_str())));
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::PStrLocation(..),
                            Addr::Str(s),
                        ) => {
                            if let &HeapCellValue::NamedStr(a1, ref n1, _) = &self.heap[s] {
                                if a1 != 2 || n1.as_str() != "." {
                                    return Some(a1.cmp(&2).then_with(|| n1.as_str().cmp(".")));
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        (
                            Addr::Str(s),
                            Addr::PStrLocation(..),
                        ) => {
                            if let &HeapCellValue::NamedStr(a1, ref n1, _) = &self.heap[s] {
                                if a1 != 2 || n1.as_str() != "." {
                                    return Some(2.cmp(&a1).then_with(|| ".".cmp(n1.as_str())));
                                }
                            } else {
                                unreachable!()
                            }
                        }
                        _ => {
                            return None;
                        }
                    }
                }
                None => {
                    return None;
                }
            }
        }

        Some(iter.first_to_expire)
    }

    pub(super)
    fn reset_block(&mut self, addr: Addr) {
        match self.store(addr) {
            Addr::Usize(b) => self.block = b,
            _ => self.fail = true,
        };
    }

    pub(super)
    fn execute_inlined(&mut self, inlined: &InlinedClauseType) {
        match inlined {
            &InlinedClauseType::CompareNumber(cmp, ref at_1, ref at_2) => {
                let n1 = try_or_fail!(self, self.get_number(at_1));
                let n2 = try_or_fail!(self, self.get_number(at_2));

                self.compare_numbers(cmp, n1, n2);
            }
            &InlinedClauseType::IsAtom(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::Con(h) =>
                        if let HeapCellValue::Atom(..) = &self.heap[h] {
                            self.p += 1;
                        } else {
                            self.fail = true;
                        },
                    Addr::Char(_) => self.p += 1,
                    Addr::EmptyList => self.p += 1,
                    _ => self.fail = true,
                };
            }
            &InlinedClauseType::IsAtomic(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::Char(_) |
                    Addr::Con(_) |
                    Addr::EmptyList |
                    Addr::Fixnum(_) |
                    Addr::Float(_) |
                    Addr::Usize(_) => self.p += 1,
                    _ => self.fail = true,
                };
            }
            &InlinedClauseType::IsInteger(r1) => {
                let d = self.store(self.deref(self[r1]));

                match Number::try_from((d, &self.heap)) {
                    Ok(Number::Fixnum(_)) => {
                        self.p += 1;
                    }
                    Ok(Number::Integer(_)) => {
                        self.p += 1;
                    }
                    Ok(Number::Rational(n)) => {
                        if n.denom() == &1 {
                            self.p += 1;
                        } else {
                            self.fail = true;
                        }
                    }
                    _ => {
                        self.fail = true;
                    }
                }
            }
            &InlinedClauseType::IsCompound(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::Str(_) | Addr::Lis(_) | Addr::PStrLocation(..) => self.p += 1,
                    _ => self.fail = true,
                };
            }
            &InlinedClauseType::IsFloat(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::Float(_) => self.p += 1,
                    _ => self.fail = true,
                };
            }
            &InlinedClauseType::IsRational(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::Con(h) => {
                        if let HeapCellValue::Rational(_) = &self.heap[h] {
                            self.p += 1;
                        } else {
                            self.fail = true;
                        }
                    }
                    _ => {
                        self.fail = true;
                    }
                };
            }
            &InlinedClauseType::IsNonVar(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::AttrVar(_) | Addr::HeapCell(_) | Addr::StackCell(..) => {
                        self.fail = true;
                    }
                    _ => {
                        self.p += 1;
                    }
                };
            }
            &InlinedClauseType::IsVar(r1) => {
                let d = self.store(self.deref(self[r1]));

                match d {
                    Addr::AttrVar(_) | Addr::HeapCell(_) | Addr::StackCell(_, _) => {
                        self.p += 1;
                    }
                    _ => {
                        self.fail = true;
                    }
                };
            }
        }
    }

    fn try_functor_compound_case(
        &mut self,
        name: ClauseName,
        arity: usize,
        spec: Option<SharedOpDesc>,
    ) {
        let name = self.heap.to_unifiable(HeapCellValue::Atom(name, spec));
        self.try_functor_unify_components(name, arity);
    }

    fn try_functor_unify_components(
        &mut self,
        name: Addr,
        arity: usize,
    ) {
        let a2 = self[temp_v!(2)];
        let a3 = self[temp_v!(3)];

        self.unify(a2, name);

        if !self.fail {
            self.unify(a3, Addr::Usize(arity));
        }
    }

    fn try_functor_fabricate_struct(
        &mut self,
        name: ClauseName,
        arity: usize,
        spec: Option<SharedOpDesc>,
        op_dir: &OpDir,
        r: Ref,
    ) {
        let spec = spec.and_then(|spec| {
            if spec.arity() != arity {
                fetch_op_spec(name.clone(), arity, op_dir)
            } else {
                Some(spec)
            }
        });

        let f_a = if name.as_str() == "." && arity == 2 {
            Addr::Lis(self.heap.h())
        } else {
            self.heap.to_unifiable(HeapCellValue::NamedStr(arity, name, spec))
        };

        let h = self.heap.h();

        for i in 0 .. arity {
            self.heap.push(HeapCellValue::Addr(Addr::HeapCell(h + i)));
        }

        self.bind(r, f_a);
    }

    pub(super)
    fn try_functor(&mut self, indices: &IndexStore) -> CallResult {
        let stub = MachineError::functor_stub(clause_name!("functor"), 3);
        let a1 = self.store(self.deref(self[temp_v!(1)]));

        match a1 {
            Addr::Stream(_) => {
                self.fail = true;
            }
            Addr::Char(_) | Addr::Con(_) | Addr::Fixnum(_) |
            Addr::Float(_) | Addr::EmptyList | Addr::Usize(_) => {
                self.try_functor_unify_components(a1, 0);
            }
            Addr::Str(o) => match self.heap.clone(o) {
                HeapCellValue::NamedStr(arity, name, spec) => {
                    let spec = fetch_op_spec_from_existing(
                        name.clone(),
                        arity,
                        spec,
                        &indices.op_dir,
                    );

                    self.try_functor_compound_case(name, arity, spec)
                }
                _ => {
                    self.fail = true;
                }
            },
            Addr::Lis(_) | Addr::PStrLocation(..) => {
                let spec = fetch_op_spec_from_existing(
                    clause_name!("."),
                    2,
                    None,
                    &indices.op_dir,
                );

                self.try_functor_compound_case(clause_name!("."), 2, spec)
            }
            Addr::AttrVar(..) | Addr::HeapCell(_) | Addr::StackCell(..) => {
                let name  = self.store(self.deref(self[temp_v!(2)]));
                let arity = self.store(self.deref(self[temp_v!(3)]));

                if name.is_ref() || arity.is_ref() {
                    // 8.5.1.3 a) & 8.5.1.3 b)
                    return Err(self.error_form(MachineError::instantiation_error(), stub));
                }

                let arity =
                    match Number::try_from((arity, &self.heap)) {
                        Ok(Number::Fixnum(n))  => Some(n),
                        Ok(Number::Integer(n)) => n.to_isize(),
                        Ok(Number::Rational(n))
                            if n.denom() == &1 => {
                                n.numer().to_isize()
                            },
                        _ =>
                            match arity {
                                arity => {
                                    return Err(
                                        self.error_form(
                                            MachineError::type_error(
                                                self.heap.h(),
                                                ValidType::Integer,
                                                arity,
                                            ),
                                            stub,
                                        )
                                    );
                                }
                            }
                    };

                let arity = match arity {
                    Some(arity) => {
                        arity
                    }
                    None => {
                        self.fail = true;
                        return Ok(());
                    }
                };

                if arity > MAX_ARITY as isize {
                    // 8.5.1.3 f)
                    let rep_err = MachineError::representation_error(RepFlag::MaxArity);
                    return Err(self.error_form(rep_err, stub));
                } else if arity < 0 {
                    // 8.5.1.3 g)
                    let arity = Number::Integer(Rc::new(Integer::from(arity)));
                    let dom_err = MachineError::domain_error(
                        DomainErrorType::NotLessThanZero,
                        arity,
                    );

                    return Err(self.error_form(dom_err, stub));
                }

                match name {
                    Addr::Char(_) | Addr::Con(_) | Addr::Fixnum(_) | Addr::Float(_) |
                    Addr::EmptyList | Addr::PStrLocation(..) | Addr::Usize(_) if arity == 0 => {
                        self.unify(a1, name);
                    }
                    Addr::Con(h) => {
                        if let HeapCellValue::Atom(name, spec) = self.heap.clone(h) {
                            self.try_functor_fabricate_struct(
                                name,
                                arity as usize,
                                spec,
                                &indices.op_dir,
                                a1.as_var().unwrap(),
                            );
                        } else {
                            // 8.5.1.3 e)
                            return Err(self.error_form(
                                MachineError::type_error(
                                    self.heap.h(),
                                    ValidType::Atom,
                                    name,
                                ),
                                stub,
                            ))
                        }
                    }
                    Addr::Char(c) => {
                        self.try_functor_fabricate_struct(
                            clause_name!(c.to_string(), indices.atom_tbl),
                            arity as usize,
                            None,
                            &indices.op_dir,
                            a1.as_var().unwrap(),
                        );
                    }
                    _ => {
                        return Err(self.error_form(
                            MachineError::type_error(self.heap.h(), ValidType::Atomic, name),
                            stub,
                        ));
                    } // 8.5.1.3 c)
                }
            }
            _ => {
                self.fail = true;
            }
        }

        Ok(())
    }

    pub(super)
    fn term_dedup(&self, list: &mut Vec<Addr>) {
        let mut result = vec![];

        for a2 in list.iter() {
            if let Some(a1) = result.last() {
                if self.compare_term_test(&a1, &a2) == Some(Ordering::Equal) {
                    continue;
                }
            }

            result.push(*a2);
        }

        *list = result;
    }


    pub(super)
    fn integers_to_bytevec(
        &self,
        r: RegType,
        caller: MachineStub,
    ) -> Vec<u8> {

        let mut bytes: Vec<u8> = Vec::new();

        match self.try_from_list(r, caller) {
            Err(_) => { unreachable!() }
            Ok(addrs) => {

                for addr in addrs {
                    let addr = self.store(self.deref(addr));

                    match Number::try_from((addr, &self.heap)) {
                        Ok(Number::Fixnum(n)) => {
                            match u8::try_from(n) {
                                Ok(b) => {
                                    bytes.push(b);
                                }
                                Err(_) => { }
                            }

                            continue;
                        }
                        Ok(Number::Integer(n)) => {
                            if let Some(b) = n.to_u8() {
                               bytes.push(b);
                            }

                            continue;
                        }
                        _ => {
                        }
                    }
                }
            }
        }
        bytes
    }


    pub(super)
    fn try_from_list(
        &self,
        r: RegType,
        caller: MachineStub,
    ) -> Result<Vec<Addr>, MachineStub> {
        let a1 = self.store(self.deref(self[r]));

        match a1 {
            Addr::Lis(l) => {
                self.try_from_inner_list(vec![], l, caller, a1)
            }
            Addr::PStrLocation(h, n) => {
                self.try_from_partial_string(vec![], h, n, caller, a1)
            }
            Addr::AttrVar(_) | Addr::HeapCell(_) | Addr::StackCell(..) => {
                Err(self.error_form(MachineError::instantiation_error(), caller))
            }
            Addr::EmptyList => {
                Ok(vec![])
            }
            _ => {
                Err(self.error_form(
                    MachineError::type_error(self.heap.h(), ValidType::List, a1),
                    caller,
                ))
            }
        }
    }

    fn try_from_inner_list(
        &self,
        mut result: Vec<Addr>,
        mut l: usize,
        caller: MachineStub,
        a1: Addr,
    ) -> Result<Vec<Addr>, MachineStub> {
        result.push(self.heap[l].as_addr(l));
        l += 1;

        loop {
            match &self.heap[l] {
                HeapCellValue::Addr(ref addr) =>
                    match self.store(self.deref(*addr)) {
                        Addr::Lis(hcp) => {
                            result.push(self.heap[hcp].as_addr(hcp));
                            l = hcp + 1;
                        }
                        Addr::PStrLocation(h, n) => {
                            return self.try_from_partial_string(result, h, n, caller, a1);
                        }
                        Addr::EmptyList => {
                            break;
                        }
                        Addr::AttrVar(_) | Addr::HeapCell(_) | Addr::StackCell(..) => {
                            return Err(self.error_form(
                                MachineError::instantiation_error(),
                                caller,
                            ))
                        }
                        _ => {
                            return Err(self.error_form(
                                MachineError::type_error(self.heap.h(), ValidType::List, a1),
                                caller,
                            ))
                        }
                    },
                _ => {
                    return Err(self.error_form(
                        MachineError::type_error(self.heap.h(), ValidType::List, a1),
                        caller,
                    ))
                }
            }
        }

        Ok(result)
    }

    fn try_from_partial_string(
        &self,
        mut chars: Vec<Addr>,
        mut h: usize,
        mut n: usize,
        caller: MachineStub,
        a1: Addr,
    ) -> Result<Vec<Addr>, MachineStub> {
        loop {
            if let &HeapCellValue::PartialString(ref pstr, has_tail) = &self.heap[h] {
                chars.extend(pstr.range_from(n ..).map(Addr::Char));

                if !has_tail {
                    return Ok(chars);
                }

                let tail = self.heap[h + 1].as_addr(h + 1);

                match self.store(self.deref(tail)) {
                    Addr::EmptyList => {
                        return Ok(chars);
                    }
                    Addr::Lis(l) => {
                        return self.try_from_inner_list(chars, l, caller, a1);
                    }
                    Addr::PStrLocation(h1, n1) => {
                        chars.push(Addr::Char('\u{0}'));

                        h = h1;
                        n = n1;
                    }
                    _ => {
                        return Err(self.error_form(
                            MachineError::type_error(self.heap.h(), ValidType::List, a1),
                            caller,
                        ))
                    }
                }
            } else {
                unreachable!()
            }
        }
    }

    // see 8.4.4.3 of Draft Technical Corrigendum 2 for an error guide.
    pub(super) fn project_onto_key(&self, a: Addr) -> Result<Addr, MachineStub> {
        let stub = MachineError::functor_stub(clause_name!("keysort"), 2);

        match self.store(self.deref(a)) {
            Addr::HeapCell(_) | Addr::StackCell(..) => {
                Err(self.error_form(MachineError::instantiation_error(), stub))
            }
            Addr::Str(s) => {
                match self.heap.clone(s) {
                    HeapCellValue::NamedStr(2, ref name, Some(_))
                        if *name == clause_name!("-") => {
                            Ok(Addr::HeapCell(s + 1))
                        }
                    _ => {
                        Err(self.error_form(
                            MachineError::type_error(
                                self.heap.h(),
                                ValidType::Pair,
                                self.heap[s].as_addr(s),
                            ),
                            stub,
                        ))
                    }
                }
            }
            a => {
                Err(self.error_form(
                    MachineError::type_error(
                        self.heap.h(),
                        ValidType::Pair,
                        a,
                    ),
                    stub,
                ))
            }
        }
    }

    pub(super)
    fn copy_term(&mut self, attr_var_policy: AttrVarPolicy) {
        let old_h = self.heap.h();

        let a1 = self[temp_v!(1)];
        let a2 = self[temp_v!(2)];

        copy_term(CopyTerm::new(self), a1, attr_var_policy);

        self.unify(Addr::HeapCell(old_h), a2);
    }

    // returns true on failure.
    pub(super)
    fn structural_eq_test(&self) -> bool {
        let a1 = self[temp_v!(1)];
        let a2 = self[temp_v!(2)];

        let mut var_pairs = IndexMap::new();

        let iter = self.zipped_acyclic_pre_order_iter(a1, a2);

        for (v1, v2) in iter {
            match (
                self.heap.index_addr(&v1).as_ref(),
                self.heap.index_addr(&v2).as_ref(),
            ) {
                (
                    HeapCellValue::Addr(Addr::Lis(_)),
                    HeapCellValue::Addr(Addr::PStrLocation(..)),
                )
                | (
                    HeapCellValue::Addr(Addr::PStrLocation(..)),
                    HeapCellValue::Addr(Addr::Lis(_)),
                ) => {
                }
                (
                    HeapCellValue::NamedStr(ar1, n1, _),
                    HeapCellValue::NamedStr(ar2, n2, _),
                ) => {
                    if ar1 != ar2 || n1 != n2 {
                        return true;
                    }
                }
                (
                    HeapCellValue::Addr(Addr::Lis(_)),
                    HeapCellValue::Addr(Addr::Lis(_)),
                ) => {
                }
                (
                    &HeapCellValue::Addr(v1 @ Addr::HeapCell(_)),
                    &HeapCellValue::Addr(v2 @ Addr::AttrVar(_)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::StackCell(..)),
                    &HeapCellValue::Addr(v2 @ Addr::AttrVar(_)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::AttrVar(_)),
                    &HeapCellValue::Addr(v2 @ Addr::AttrVar(_)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::AttrVar(_)),
                    &HeapCellValue::Addr(v2 @ Addr::HeapCell(_)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::AttrVar(_)),
                    &HeapCellValue::Addr(v2 @ Addr::StackCell(..)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::HeapCell(_)),
                    &HeapCellValue::Addr(v2 @ Addr::HeapCell(_)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::HeapCell(_)),
                    &HeapCellValue::Addr(v2 @ Addr::StackCell(..)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::StackCell(..)),
                    &HeapCellValue::Addr(v2 @ Addr::StackCell(..)),
                )
                | (
                    &HeapCellValue::Addr(v1 @ Addr::StackCell(..)),
                    &HeapCellValue::Addr(v2 @ Addr::HeapCell(_)),
                ) =>
                    match (var_pairs.get(&v1), var_pairs.get(&v2)) {
                        (Some(ref v2_p), Some(ref v1_p)) if **v1_p == v1 && **v2_p == v2 => {
                            continue;
                        }
                        (Some(_), _) | (_, Some(_)) => {
                            return true;
                        }
                        (None, None) => {
                            var_pairs.insert(v1, v2);
                            var_pairs.insert(v2, v1);
                        }
                    },
                (
                    HeapCellValue::PartialString(ref pstr1, has_tail_1),
                    HeapCellValue::PartialString(ref pstr2, has_tail_2),
                ) => {
                    if has_tail_1 != has_tail_2 {
                        return true;
                    }

                    let pstr1_iter = pstr1.range_from(0 ..);
                    let pstr2_iter = pstr2.range_from(0 ..);

                    for (c1, c2) in pstr1_iter.zip(pstr2_iter) {
                        if c1 != c2 {
                            return true;
                        }
                    }
                }
                (
                    HeapCellValue::Addr(Addr::PStrLocation(..)),
                    HeapCellValue::Addr(Addr::PStrLocation(..)),
                ) => {
                }
                (
                    HeapCellValue::Atom(ref n1, ref spec_1),
                    HeapCellValue::Atom(ref n2, ref spec_2),
                ) => {
                    if n1 != n2 || spec_1 != spec_2 {
                        return true;
                    }
                }
                (
                    HeapCellValue::DBRef(ref db_ref_1),
                    HeapCellValue::DBRef(ref db_ref_2),
                ) => {
                    if db_ref_1 != db_ref_2 {
                        return true;
                    }
                }
                (
                    v1,
                    v2,
                ) => {
                    if let Ok(n1) = Number::try_from(v1) {
                        if let Ok(n2) = Number::try_from(v2) {
                            if n1 != n2 {
                                return true;
                            } else {
                                continue;
                            }
                        } else {
                            return true;
                        }
                    }

                    match (v1, v2) {
                        (
                            HeapCellValue::Addr(a1),
                            HeapCellValue::Addr(a2),
                        ) => {
                            if a1 != a2 {
                                return true;
                            }
                        }
                        _ => {
                            return true;
                        }
                    }
                }
            }
        }

        false
    }

    // returns true on failure.
    pub(super)
    fn ground_test(&self) -> bool {
        let a = self.store(self.deref(self[temp_v!(1)]));

        for v in self.acyclic_pre_order_iter(a) {
            match v {
                Addr::HeapCell(..) => return true,
                Addr::StackCell(..) => return true,
                Addr::AttrVar(..) => return true,
                _ => {}
            }
        }

        false
    }

    pub(super)
    fn setup_built_in_call(&mut self, ct: BuiltInClauseType) {
        self.num_of_args = ct.arity();
        self.b0 = self.b;

        self.p = CodePtr::BuiltInClause(ct, self.p.local());
    }

    pub(super)
    fn allocate(&mut self, num_cells: usize) {
        let e = self.stack.allocate_and_frame(num_cells);
        let and_frame = self.stack.index_and_frame_mut(e);

        and_frame.prelude.e  = self.e;
        and_frame.prelude.cp = self.cp;

        self.e = e;
        self.p += 1;
    }

    pub(super)
    fn deallocate(&mut self) {
        let e = self.e;
        let frame = self.stack.index_and_frame(e);

        self.cp = frame.prelude.cp;
        self.e  = frame.prelude.e;

        if e > self.b {
            self.stack.truncate(e);
        }

        self.p += 1;
    }

    fn throw_interrupt_exception(&mut self) {
        let err = MachineError::interrupt_error();
        let src = functor!("repl");
        let err = self.error_form(err, src);

        self.throw_exception(err);
    }

    fn handle_call_clause(
        &mut self,
        indices: &mut IndexStore,
        code_repo: &CodeRepo,
        call_policy: &mut Box<dyn CallPolicy>,
        cut_policy: &mut Box<dyn CutPolicy>,
        current_input_stream: &mut Stream,
        current_output_stream: &mut Stream,
        ct: &ClauseType,
        arity: usize,
        lco: bool,
        use_default_cp: bool,
    ) {
	    let interrupted = INTERRUPT.load(std::sync::atomic::Ordering::Relaxed);

	    if INTERRUPT.compare_and_swap(interrupted, false, std::sync::atomic::Ordering::Relaxed) {
            self.throw_interrupt_exception();
	        return;
	    }

        let mut default_call_policy: Box<dyn CallPolicy> = Box::new(DefaultCallPolicy {});

        let call_policy = if use_default_cp {
            &mut default_call_policy
        } else {
            call_policy
        };

        self.last_call = lco;

        match ct {
            &ClauseType::BuiltIn(ref ct) => try_or_fail!(
                self,
                call_policy.call_builtin(
                    self,
                    ct,
                    indices,
                    current_input_stream,
                    current_output_stream,
                )
            ),
            &ClauseType::CallN => try_or_fail!(
                self,
                call_policy.call_n(self, arity, indices, current_input_stream, current_output_stream)
            ),
            &ClauseType::Hook(ref hook) => try_or_fail!(self, call_policy.compile_hook(self, hook)),
            &ClauseType::Inlined(ref ct) => {
                self.execute_inlined(ct);

                if lco {
                    self.p = CodePtr::Local(self.cp);
                }
            }
            &ClauseType::Named(ref name, _, ref idx) | &ClauseType::Op(ref name, _, ref idx) => {
                try_or_fail!(
                    self,
                    call_policy.context_call(self, name.clone(), arity, idx.clone(), indices)
                )
            }
            &ClauseType::System(ref ct) => try_or_fail!(
                self,
                self.system_call(
                    ct,
                    code_repo,
                    indices,
                    call_policy,
                    cut_policy,
                    current_input_stream,
                    current_output_stream,
                )
            ),
        };

        self.last_call = false;
    }

    pub(super)
    fn execute_ctrl_instr(
        &mut self,
        indices: &mut IndexStore,
        code_repo: &CodeRepo,
        call_policy: &mut Box<dyn CallPolicy>,
        cut_policy: &mut Box<dyn CutPolicy>,
        current_input_stream: &mut Stream,
        current_output_stream: &mut Stream,
        instr: &ControlInstruction,
    ) {
        match instr {
            &ControlInstruction::Allocate(num_cells) => {
                self.allocate(num_cells);
            }
            &ControlInstruction::CallClause(ref ct, arity, _, lco, use_default_cp) => self
                .handle_call_clause(
                    indices,
                    code_repo,
                    call_policy,
                    cut_policy,
                    current_input_stream,
                    current_output_stream,
                    ct,
                    arity,
                    lco,
                    use_default_cp,
                ),
            &ControlInstruction::Deallocate => self.deallocate(),
            &ControlInstruction::JmpBy(arity, offset, _, lco) => {
                if !lco {
                    self.cp.assign_if_local(self.p.clone() + 1);
                }

                self.num_of_args = arity;
                self.b0 = self.b;
                self.p += offset;
            }
            &ControlInstruction::Proceed => {
                self.p = CodePtr::Local(self.cp);
            }
        };
    }

    pub(super) fn execute_indexed_choice_instr(
        &mut self,
        instr: &IndexedChoiceInstruction,
        call_policy: &mut Box<dyn CallPolicy>,
    ) {
        match instr {
            &IndexedChoiceInstruction::Try(offset) => {
                let n = self.num_of_args;
                let b = self.stack.allocate_or_frame(n);
                let or_frame = self.stack.index_or_frame_mut(b);

                or_frame.prelude.univ_prelude.num_cells = n;
                or_frame.prelude.e = self.e;
                or_frame.prelude.cp = self.cp;
                or_frame.prelude.b = self.b;
                or_frame.prelude.bp = self.p.local() + 1;
                or_frame.prelude.tr = self.tr;
                or_frame.prelude.h  = self.heap.h();
                or_frame.prelude.b0 = self.b0;

                or_frame.prelude.attr_var_init_queue_b =
                    self.attr_var_init.attr_var_queue.len();
                or_frame.prelude.attr_var_init_bindings_b =
                    self.attr_var_init.bindings.len();

                self.b = b;

                for i in 1 .. n + 1 {
                    self.stack.index_or_frame_mut(b)[i-1] = self.registers[i];
                }

                self.hb = self.heap.h();
                self.p += offset;
            }
            &IndexedChoiceInstruction::Retry(l) => {
                try_or_fail!(self, call_policy.retry(self, l));
            }
            &IndexedChoiceInstruction::Trust(l) => {
                try_or_fail!(self, call_policy.trust(self, l));
            }
        };
    }

    pub(super) fn execute_choice_instr(
        &mut self,
        instr: &ChoiceInstruction,
        call_policy: &mut Box<dyn CallPolicy>,
    ) {
        match instr {
            &ChoiceInstruction::TryMeElse(offset) => {
                let n = self.num_of_args;
                let b = self.stack.allocate_or_frame(n);
                let or_frame = self.stack.index_or_frame_mut(b);

                or_frame.prelude.univ_prelude.num_cells = n;
                or_frame.prelude.e = self.e;
                or_frame.prelude.cp = self.cp;
                or_frame.prelude.b = self.b;
                or_frame.prelude.bp = self.p.local() + offset;
                or_frame.prelude.tr = self.tr;
                or_frame.prelude.h  = self.heap.h();
                or_frame.prelude.b0 = self.b0;
                or_frame.prelude.attr_var_init_queue_b =
                    self.attr_var_init.attr_var_queue.len();
                or_frame.prelude.attr_var_init_bindings_b =
                    self.attr_var_init.attr_var_queue.len();

                self.b = b;

                for i in 1 .. n + 1  {
                    self.stack.index_or_frame_mut(b)[i-1] = self.registers[i];
                }

                self.hb = self.heap.h();
                self.p += 1;
            }
            &ChoiceInstruction::DefaultRetryMeElse(offset) => {
                let mut call_policy = DefaultCallPolicy {};
                try_or_fail!(self, call_policy.retry_me_else(self, offset))
            }
            &ChoiceInstruction::DefaultTrustMe => {
                let mut call_policy = DefaultCallPolicy {};
                try_or_fail!(self, call_policy.trust_me(self))
            }
            &ChoiceInstruction::RetryMeElse(offset) => {
                try_or_fail!(self, call_policy.retry_me_else(self, offset))
            }
            &ChoiceInstruction::TrustMe => {
                try_or_fail!(self, call_policy.trust_me(self))
            }
        }
    }

    pub(super) fn execute_cut_instr(
        &mut self,
        instr: &CutInstruction,
        cut_policy: &mut Box<dyn CutPolicy>,
    ) {
        match instr {
            &CutInstruction::NeckCut => {
                let b = self.b;
                let b0 = self.b0;

                if b > b0 {
                    self.b = b0;
                    self.tidy_trail();

                    if b > self.e {
                        self.stack.truncate(b);
                    }
                }

                self.p += 1;
            }
            &CutInstruction::GetLevel(r) => {
                let b0 = self.b0;

                self[r] = Addr::CutPoint(b0);
                self.p += 1;
            }
            &CutInstruction::GetLevelAndUnify(r) => {
                let b0 = self[perm_v!(1)];
                let a = self[r];

                self.unify(a, b0);
                self.p += 1;
            }
            &CutInstruction::Cut(r) => {
                if !cut_policy.cut(self, r) {
                    self.p += 1;
                }
            }
        }
    }

    pub fn reset(&mut self) {
        self.stack.drop_in_place();

        self.hb = 0;
        self.e = 0;
        self.b = 0;
        self.b0 = 0;
        self.s = HeapPtr::default();
        self.tr = 0;
        self.p = CodePtr::default();
        self.cp = LocalCodePtr::default();
        self.attr_var_init.reset();
        self.num_of_args = 0;

        self.fail = false;
        self.trail.clear();
        self.heap.clear();
        self.mode = MachineMode::Write;
        self.registers = vec![Addr::HeapCell(0); MAX_ARITY + 1]; // self.registers[0] is never used.
        self.block = 0;

        self.ball.reset();
        self.heap_locs.clear();
        self.lifted_heap.clear();
    }
}
