use crate::cell::{self, Word};
use plg_shared::StringInterner;
pub type ContFn = unsafe extern "C" fn(*mut Machine, u64) -> i32;
pub const MAX_ARGS: usize = 16;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct RegistryEntry {
pub functor: u32,
pub arity: u32,
pub f: ContFn,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SrcLoc {
pub file: u32,
pub line: u32,
pub col: u32,
}
pub const NO_SITE: u32 = u32::MAX;
#[must_use = "binding to `let _` drops the guard immediately; use `let _site = ...`"]
pub(crate) struct ErrorSiteGuard {
m: *mut Machine,
saved: u32,
}
impl ErrorSiteGuard {
pub(crate) fn enter(m: *mut Machine, site_id: u32) -> Self {
let saved = unsafe { (*m).error_site };
unsafe { (*m).error_site = site_id };
ErrorSiteGuard { m, saved }
}
}
impl Drop for ErrorSiteGuard {
fn drop(&mut self) {
unsafe { (*self.m).error_site = self.saved };
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum CpKind {
Normal,
Catch,
}
pub struct ChoicePoint {
pub trail_mark: usize,
pub heap_mark: usize,
pub retry: ContFn,
pub env: u64,
pub kind: CpKind,
}
pub struct RtError {
pub ball: crate::copyterm::TermBuf,
pub message: String,
pub uncatchable: bool,
}
pub struct Machine {
pub heap: Vec<Word>,
pub trail: Vec<u64>, pub cps: Vec<ChoicePoint>,
pub areg: [Word; MAX_ARGS],
pub breg: [Word; MAX_ARGS],
pub k_fn: ContFn,
pub k_env: u64,
pub steps: u64,
pub step_limit: u64,
pub error: Option<RtError>,
pub atoms: StringInterner,
pub registry: Vec<RegistryEntry>,
pub srcmap: Vec<SrcLoc>,
pub files: Vec<String>,
pub error_site: u32,
pub query_vars: Vec<(String, usize)>,
pub findall_stack: Vec<Vec<crate::copyterm::TermBuf>>,
pub qbarrier: usize,
pub solutions: Vec<crate::render::RenderedSolution>,
pub solution_limit: Option<usize>,
}
unsafe extern "C" fn no_continuation(_m: *mut Machine, _env: u64) -> i32 {
debug_assert!(false, "no continuation installed");
0
}
impl Machine {
pub fn new(atoms: StringInterner, registry: Vec<RegistryEntry>) -> Box<Machine> {
Box::new(Machine {
heap: Vec::with_capacity(4096),
trail: Vec::with_capacity(256),
cps: Vec::with_capacity(64),
areg: [0; MAX_ARGS],
breg: [0; MAX_ARGS],
k_fn: no_continuation,
k_env: 0,
steps: 0,
step_limit: 10_000, error: None,
atoms,
registry,
srcmap: Vec::new(),
files: Vec::new(),
error_site: NO_SITE,
query_vars: Vec::new(),
findall_stack: Vec::new(),
qbarrier: 0,
solutions: Vec::new(),
solution_limit: None,
})
}
pub fn new_var(&mut self) -> Word {
let idx = self.heap.len();
self.heap.push(cell::make_ref(idx)); cell::make_ref(idx)
}
pub fn frame_alloc(&mut self, n: usize) -> usize {
let idx = self.heap.len();
self.heap.resize(idx + n, 0);
idx
}
pub fn bind(&mut self, ref_idx: usize, value: Word) {
debug_assert_eq!(
self.heap[ref_idx],
cell::make_ref(ref_idx),
"bind target must be unbound"
);
self.heap[ref_idx] = value;
self.trail.push(ref_idx as u64);
}
pub fn deref(&self, mut w: Word) -> Word {
while cell::tag_of(w) == cell::TAG_REF {
let idx = cell::payload(w) as usize;
let c = self.heap[idx];
if c == w {
return w; }
w = c;
}
w
}
pub fn push_cp(&mut self, retry: ContFn, env: u64) {
self.cps.push(ChoicePoint {
trail_mark: self.trail.len(),
heap_mark: self.heap.len(),
retry,
env,
kind: CpKind::Normal,
});
}
pub fn push_cp_at(&mut self, retry: ContFn, env: u64, trail_mark: usize, heap_mark: usize) {
debug_assert!(trail_mark <= self.trail.len() && heap_mark <= self.heap.len());
self.cps.push(ChoicePoint {
trail_mark,
heap_mark,
retry,
env,
kind: CpKind::Normal,
});
}
pub fn push_catch_cp(&mut self, retry: ContFn, env: u64) {
self.cps.push(ChoicePoint {
trail_mark: self.trail.len(),
heap_mark: self.heap.len(),
retry,
env,
kind: CpKind::Catch,
});
}
pub fn cut_to(&mut self, height: usize) {
while self.cps.len() > height {
if self.cps.last().is_some_and(|cp| cp.kind == CpKind::Catch) {
break;
}
self.cps.pop();
}
}
pub fn rewind_to(&mut self, trail_mark: usize, heap_mark: usize) {
while self.trail.len() > trail_mark {
let idx = self.trail.pop().unwrap() as usize;
self.heap[idx] = cell::make_ref(idx);
}
self.heap.truncate(heap_mark);
}
pub fn step(&mut self) -> bool {
self.steps += 1;
if self.steps > self.step_limit {
let context = format!("Maximum step limit exceeded ({})", self.step_limit);
crate::errors::resource(self, "steps", &context, true);
return false;
}
true
}
pub fn registry_lookup(&self, functor: u32, arity: u32) -> Option<ContFn> {
self.registry
.binary_search_by_key(&(functor, arity), |e| (e.functor, e.arity))
.ok()
.map(|i| self.registry[i].f)
}
pub fn set_provenance(&mut self, srcmap: Vec<SrcLoc>, files: Vec<String>) {
self.srcmap = srcmap;
self.files = files;
}
pub fn site_location(&self, site_id: u32) -> Option<(String, u32, u32)> {
if site_id == NO_SITE {
return None;
}
let loc = self.srcmap.get(site_id as usize)?;
let file = self.files.get(loc.file as usize)?;
Some((file.clone(), loc.line, loc.col))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cell::*;
fn machine() -> Box<Machine> {
Machine::new(StringInterner::new(), Vec::new())
}
#[test]
fn new_var_is_unbound_self_ref() {
let mut m = machine();
let v = m.new_var();
assert_eq!(tag_of(v), TAG_REF);
assert_eq!(m.deref(v), v);
}
#[test]
fn no_site_sentinel_value_is_pinned() {
assert_eq!(NO_SITE, u32::MAX);
}
#[test]
fn bind_and_rewind() {
let mut m = machine();
let v = m.new_var();
let tmark = m.trail.len();
let hmark = m.heap.len();
m.bind(payload(v) as usize, make_atom(7));
assert_eq!(m.deref(v), make_atom(7));
m.rewind_to(tmark, hmark);
assert_eq!(m.deref(v), v, "binding undone");
}
#[test]
fn deref_follows_chains() {
let mut m = machine();
let a = m.new_var();
let b = m.new_var();
m.bind(payload(a) as usize, b);
m.bind(payload(b) as usize, make_int(-5));
assert_eq!(int_value(m.deref(a)), -5);
}
#[test]
fn step_limit_sets_uncatchable_error() {
let mut m = machine();
m.step_limit = 2;
assert!(m.step());
assert!(m.step());
assert!(!m.step());
assert!(m.error.as_ref().unwrap().uncatchable);
}
}