Merge branch 'master' into library-use-case
# Conflicts: # Cargo.lock # Cargo.toml # src/bin/scryer-prolog.rs # src/loader.pl # src/machine/mock_wam.rs # src/machine/mod.rs # src/machine/system_calls.rs
This commit is contained in:
+23
-39
@@ -1,14 +1,10 @@
|
||||
use crate::parser::ast::*;
|
||||
use crate::temp_v;
|
||||
|
||||
use crate::fixtures::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::targets::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) trait Allocator {
|
||||
fn new() -> Self;
|
||||
@@ -17,7 +13,7 @@ pub(crate) trait Allocator {
|
||||
&mut self,
|
||||
lvl: Level,
|
||||
context: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||
@@ -25,83 +21,71 @@ pub(crate) trait Allocator {
|
||||
lvl: Level,
|
||||
context: GenContext,
|
||||
cell: &'a Cell<RegType>,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_name: Rc<String>,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
r: RegType,
|
||||
is_new_var: bool,
|
||||
);
|
||||
|
||||
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType;
|
||||
|
||||
fn mark_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_name: Rc<String>,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
context: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
fn reset(&mut self);
|
||||
fn reset_contents(&mut self) {}
|
||||
fn reset_arg(&mut self, arg_num: usize);
|
||||
fn reset_at_head(&mut self, args: &Vec<Term>);
|
||||
fn reset_contents(&mut self);
|
||||
|
||||
fn advance_arg(&mut self);
|
||||
|
||||
/*
|
||||
fn bindings(&self) -> &AllocVarDict;
|
||||
fn bindings_mut(&mut self) -> &mut AllocVarDict;
|
||||
|
||||
fn take_bindings(self) -> AllocVarDict;
|
||||
*/
|
||||
|
||||
fn max_reg_allocated(&self) -> usize;
|
||||
|
||||
// TODO: wha.. why?? grrr. it drains the VarStatus data from vs (which it owns!)
|
||||
// into self.bindings and perm_vs after all is computed (i.e. vs.populate_restricting_sets()
|
||||
// and vs.set_perm_vals(has_deep_cut) have both been called).
|
||||
/*
|
||||
fn drain_var_data<'a>(
|
||||
&mut self,
|
||||
vs: VariableFixtures<'a>,
|
||||
vs: VariableFixtures,
|
||||
num_of_chunks: usize,
|
||||
) -> VariableFixtures<'a> {
|
||||
) -> VariableFixtures {
|
||||
let mut perm_vs = VariableFixtures::new();
|
||||
|
||||
for (var, (var_status, cells)) in vs.into_iter() {
|
||||
for (var, var_status) in vs.into_iter() {
|
||||
match var_status {
|
||||
VarStatus::Temp(chunk_num, tvd) => {
|
||||
self.bindings_mut()
|
||||
.insert(var.clone(), VarData::Temp(chunk_num, 0, tvd));
|
||||
|
||||
if chunk_num + 1 == num_of_chunks {
|
||||
perm_vs.insert_last_chunk_temp_var(var);
|
||||
}
|
||||
.insert(var.clone(), VarAlloc::Temp(chunk_num, 0, tvd));
|
||||
}
|
||||
VarStatus::Perm(_) => {
|
||||
self.bindings_mut().insert(var.clone(), VarData::Perm(0));
|
||||
perm_vs.insert(var, (var_status, cells));
|
||||
self.bindings_mut().insert(var.clone(), VarAlloc::Perm(0));
|
||||
perm_vs.insert(var, var_status);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
perm_vs
|
||||
}
|
||||
|
||||
fn get(&self, var: Rc<String>) -> RegType {
|
||||
self.bindings()
|
||||
.get(&var)
|
||||
.map_or(temp_v!(0), |v| v.as_reg_type())
|
||||
}
|
||||
|
||||
fn is_unbound(&self, var: Rc<String>) -> bool {
|
||||
self.get(var).reg_num() == 0
|
||||
}
|
||||
|
||||
fn record_register(&mut self, var: Rc<String>, r: RegType) {
|
||||
match self.bindings_mut().get_mut(&var).unwrap() {
|
||||
&mut VarData::Temp(_, ref mut s, _) => *s = r.reg_num(),
|
||||
&mut VarData::Perm(ref mut s) => *s = r.reg_num(),
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
+16
-14
@@ -6,7 +6,7 @@ use crate::raw_block::*;
|
||||
use crate::read::*;
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
|
||||
use std::alloc;
|
||||
use std::fmt;
|
||||
@@ -242,9 +242,11 @@ impl<T: fmt::Display> fmt::Display for TypedArenaPtr<T> {
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ArenaAllocated> TypedArenaPtr<T> {
|
||||
// data must be allocated in the arena already.
|
||||
#[inline]
|
||||
pub const fn new(data: *mut T) -> Self {
|
||||
unsafe { TypedArenaPtr(ptr::NonNull::new_unchecked(data)) }
|
||||
let result = unsafe { TypedArenaPtr(ptr::NonNull::new_unchecked(data)) };
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -698,9 +700,9 @@ unsafe fn drop_slab_in_place(value: &mut AllocSlab) {
|
||||
ArenaHeaderTag::HttpReadStream => {
|
||||
ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<HttpReadStream>>>());
|
||||
}
|
||||
ArenaHeaderTag::HttpWriteStream => {
|
||||
ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<HttpWriteStream>>>());
|
||||
}
|
||||
ArenaHeaderTag::HttpWriteStream => {
|
||||
ptr::drop_in_place(value.payload_offset::<StreamLayout<CharReader<HttpWriteStream>>>());
|
||||
}
|
||||
ArenaHeaderTag::ReadlineStream => {
|
||||
ptr::drop_in_place(value.payload_offset::<StreamLayout<ReadlineStream>>());
|
||||
}
|
||||
@@ -721,12 +723,12 @@ unsafe fn drop_slab_in_place(value: &mut AllocSlab) {
|
||||
ArenaHeaderTag::TcpListener => {
|
||||
ptr::drop_in_place(value.payload_offset::<TcpListener>());
|
||||
}
|
||||
ArenaHeaderTag::HttpListener => {
|
||||
ptr::drop_in_place(value.payload_offset::<HttpListener>());
|
||||
}
|
||||
ArenaHeaderTag::HttpResponse => {
|
||||
ptr::drop_in_place(value.payload_offset::<HttpResponse>());
|
||||
}
|
||||
ArenaHeaderTag::HttpListener => {
|
||||
ptr::drop_in_place(value.payload_offset::<HttpListener>());
|
||||
}
|
||||
ArenaHeaderTag::HttpResponse => {
|
||||
ptr::drop_in_place(value.payload_offset::<HttpResponse>());
|
||||
}
|
||||
ArenaHeaderTag::StandardOutputStream => {
|
||||
ptr::drop_in_place(value.payload_offset::<StreamLayout<StandardOutputStream>>());
|
||||
}
|
||||
@@ -788,7 +790,7 @@ mod tests {
|
||||
use crate::machine::partial_string::*;
|
||||
|
||||
use ordered_float::OrderedFloat;
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
|
||||
#[test]
|
||||
fn float_ptr_cast() {
|
||||
@@ -889,7 +891,7 @@ mod tests {
|
||||
|
||||
// rational
|
||||
|
||||
let big_rat = 2 * Rational::from(1u64 << 63);
|
||||
let big_rat = Rational::from(2) * Rational::from(1u64 << 63);
|
||||
let big_rat_ptr: TypedArenaPtr<Rational> = arena_alloc!(big_rat, &mut wam.machine_st.arena);
|
||||
|
||||
assert!(!big_rat_ptr.as_ptr().is_null());
|
||||
@@ -915,7 +917,7 @@ mod tests {
|
||||
(HeapCellValueTag::Cons, cons_ptr) => {
|
||||
match_untyped_arena_ptr!(cons_ptr,
|
||||
(ArenaHeaderTag::Rational, n) => {
|
||||
assert_eq!(&*n, &(2 * Rational::from(1u64 << 63)));
|
||||
assert_eq!(&*n, &(Rational::from(2) * Rational::from(1u64 << 63)));
|
||||
}
|
||||
_ => unreachable!()
|
||||
)
|
||||
|
||||
+54
-53
@@ -9,11 +9,11 @@ use crate::targets::QueryInstruction;
|
||||
use crate::types::*;
|
||||
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::rug::ops::PowAssign;
|
||||
use crate::parser::rug::{Assign, Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
|
||||
use crate::machine::machine_errors::*;
|
||||
|
||||
use dashu::base::Abs;
|
||||
use ordered_float::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
@@ -22,7 +22,6 @@ use std::convert::TryFrom;
|
||||
use std::f64;
|
||||
use std::num::FpCategory;
|
||||
use std::ops::Div;
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
@@ -53,7 +52,7 @@ pub(crate) struct ArithInstructionIterator<'a> {
|
||||
state_stack: Vec<TermIterState<'a>>,
|
||||
}
|
||||
|
||||
pub(crate) type ArithCont = (Code, Option<ArithmeticTerm>);
|
||||
pub(crate) type ArithCont = (CodeDeque, Option<ArithmeticTerm>);
|
||||
|
||||
impl<'a> ArithInstructionIterator<'a> {
|
||||
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
|
||||
@@ -67,19 +66,6 @@ impl<'a> ArithInstructionIterator<'a> {
|
||||
Term::Clause(cell, name, terms) => {
|
||||
TermIterState::Clause(Level::Shallow, 0, cell, *name, terms)
|
||||
}
|
||||
/* match ClauseType::from(*name, terms.len()) {
|
||||
ct @ ClauseType::Named(..) => {
|
||||
Ok(TermIterState::Clause(Level::Shallow, 0, cell, ct, terms))
|
||||
}
|
||||
ct @ ClauseType::Inlined(InlinedClauseType::IsFloat(_)) => {
|
||||
// let ct = ClauseType::Named(1, atom!("float"), CodeIndex::default());
|
||||
Ok(TermIterState::Clause(Level::Shallow, 0, cell, ct, terms))
|
||||
}
|
||||
_ => Err(ArithmeticError::NonEvaluableFunctor(
|
||||
Literal::Atom(*name),
|
||||
terms.len(),
|
||||
)),
|
||||
}?,*/
|
||||
Term::Literal(cell, cons) => TermIterState::Literal(Level::Shallow, cell, cons),
|
||||
Term::Cons(..) | Term::PartialString(..) | Term::CompleteString(..) => {
|
||||
return Err(ArithmeticError::NonEvaluableFunctor(
|
||||
@@ -87,7 +73,7 @@ impl<'a> ArithInstructionIterator<'a> {
|
||||
2,
|
||||
))
|
||||
}
|
||||
Term::Var(cell, var) => TermIterState::Var(Level::Shallow, cell, var.clone()),
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Shallow, cell, var_ptr.clone()),
|
||||
};
|
||||
|
||||
Ok(ArithInstructionIterator {
|
||||
@@ -100,7 +86,7 @@ impl<'a> ArithInstructionIterator<'a> {
|
||||
pub(crate) enum ArithTermRef<'a> {
|
||||
Literal(&'a Literal),
|
||||
Op(Atom, usize), // name, arity.
|
||||
Var(Level, &'a Cell<VarReg>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ArithInstructionIterator<'a> {
|
||||
@@ -128,8 +114,8 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
|
||||
}
|
||||
}
|
||||
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(c))),
|
||||
TermIterState::Var(lvl, cell, var) => {
|
||||
return Some(Ok(ArithTermRef::Var(lvl, cell, var.clone())));
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(Ok(ArithTermRef::Var(lvl, cell, var_ptr)));
|
||||
}
|
||||
_ => {
|
||||
return Some(Err(ArithmeticError::NonEvaluableFunctor(
|
||||
@@ -209,6 +195,13 @@ impl<'a> ArithmeticEvaluator<'a> {
|
||||
atom!("sin") => Ok(Instruction::Sin(a1, t)),
|
||||
atom!("tan") => Ok(Instruction::Tan(a1, t)),
|
||||
atom!("log") => Ok(Instruction::Log(a1, t)),
|
||||
atom!("asinh") => Ok(Instruction::ASinh(a1, t)),
|
||||
atom!("acosh") => Ok(Instruction::ACosh(a1, t)),
|
||||
atom!("atanh") => Ok(Instruction::ATanh(a1, t)),
|
||||
atom!("sinh") => Ok(Instruction::Sinh(a1, t)),
|
||||
atom!("cosh") => Ok(Instruction::Cosh(a1, t)),
|
||||
atom!("tanh") => Ok(Instruction::Tanh(a1, t)),
|
||||
atom!("log10") => Ok(Instruction::Log10(a1, t)),
|
||||
atom!("exp") => Ok(Instruction::Exp(a1, t)),
|
||||
atom!("sqrt") => Ok(Instruction::Sqrt(a1, t)),
|
||||
atom!("acos") => Ok(Instruction::ACos(a1, t)),
|
||||
@@ -219,6 +212,8 @@ impl<'a> ArithmeticEvaluator<'a> {
|
||||
atom!("round") => Ok(Instruction::Round(a1, t)),
|
||||
atom!("ceiling") => Ok(Instruction::Ceiling(a1, t)),
|
||||
atom!("floor") => Ok(Instruction::Floor(a1, t)),
|
||||
atom!("float_integer_part") => Ok(Instruction::FloatIntegerPart(a1, t)),
|
||||
atom!("float_fractional_part") => Ok(Instruction::FloatFractionalPart(a1, t)),
|
||||
atom!("sign") => Ok(Instruction::Sign(a1, t)),
|
||||
atom!("\\") => Ok(Instruction::BitwiseComplement(a1, t)),
|
||||
_ => Err(ArithmeticError::NonEvaluableFunctor(Literal::Atom(name), 1)),
|
||||
@@ -320,41 +315,49 @@ impl<'a> ArithmeticEvaluator<'a> {
|
||||
src: &'a Term,
|
||||
term_loc: GenContext,
|
||||
arg: usize,
|
||||
) -> Result<ArithCont, ArithmeticError>
|
||||
{
|
||||
let mut code = vec![];
|
||||
) -> Result<ArithCont, ArithmeticError> {
|
||||
let mut code = CodeDeque::new();
|
||||
let mut iter = src.iter()?;
|
||||
|
||||
while let Some(term_ref) = iter.next() {
|
||||
match term_ref? {
|
||||
ArithTermRef::Literal(c) => push_literal(&mut self.interm, c)?,
|
||||
ArithTermRef::Var(lvl, cell, name) => {
|
||||
let var_num = name.to_var_num().unwrap();
|
||||
|
||||
let r = if lvl == Level::Shallow {
|
||||
self.marker.mark_non_callable(
|
||||
name.clone(),
|
||||
var_num,
|
||||
arg,
|
||||
term_loc,
|
||||
cell,
|
||||
&mut code,
|
||||
)
|
||||
} else if term_loc.is_last() || cell.get().norm().reg_num() == 0 {
|
||||
self.marker.mark_var::<QueryInstruction>(
|
||||
name.clone(),
|
||||
lvl,
|
||||
cell,
|
||||
term_loc,
|
||||
&mut code,
|
||||
);
|
||||
let r = self.marker.get_binding(var_num);
|
||||
|
||||
self.marker.get_binding(&name).unwrap()
|
||||
if r.reg_num() == 0 {
|
||||
self.marker.mark_var::<QueryInstruction>(
|
||||
var_num,
|
||||
lvl,
|
||||
cell,
|
||||
term_loc,
|
||||
&mut code,
|
||||
);
|
||||
cell.get().norm()
|
||||
} else {
|
||||
self.marker.increment_running_count(var_num);
|
||||
r
|
||||
}
|
||||
} else {
|
||||
self.marker.increment_running_count(var_num);
|
||||
cell.get().norm()
|
||||
};
|
||||
|
||||
self.interm.push(ArithmeticTerm::Reg(r));
|
||||
}
|
||||
ArithTermRef::Op(name, arity) => {
|
||||
code.push(self.instr_from_clause(name, arity)?);
|
||||
code.push_back(self.instr_from_clause(name, arity)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,13 +386,11 @@ pub(crate) fn rnd_i<'a>(n: &'a Number, arena: &mut Arena) -> Number {
|
||||
if I64_MIN_TO_F <= f && f <= I64_MAX_TO_F {
|
||||
fixnum!(Number, f.into_inner() as i64, arena)
|
||||
} else {
|
||||
Number::Integer(arena_alloc!(Integer::from_f64(f.into_inner()).unwrap(), arena))
|
||||
Number::Integer(arena_alloc!(Integer::from(f.0 as i64), arena))
|
||||
}
|
||||
}
|
||||
&Number::Rational(ref r) => {
|
||||
let r_ref = r.fract_floor_ref();
|
||||
let (mut fract, mut floor) = (Rational::new(), Integer::new());
|
||||
(&mut fract, &mut floor).assign(r_ref);
|
||||
let (_, floor) = (r.fract(), r.floor());
|
||||
|
||||
if let Some(floor) = floor.to_i64() {
|
||||
fixnum!(Number, floor, arena)
|
||||
@@ -411,9 +412,9 @@ impl From<Fixnum> for Integer {
|
||||
pub(crate) fn rnd_f(n: &Number) -> f64 {
|
||||
match n {
|
||||
&Number::Fixnum(n) => n.get_num() as f64,
|
||||
&Number::Integer(ref n) => n.to_f64(),
|
||||
&Number::Integer(ref n) => n.to_f64().value(),
|
||||
&Number::Float(OrderedFloat(f)) => f,
|
||||
&Number::Rational(ref r) => r.to_f64(),
|
||||
&Number::Rational(ref r) => r.to_f64().value(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,12 +445,12 @@ pub(crate) fn float_fn_to_f(n: i64) -> Result<f64, EvalError> {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_i_to_f(n: &Integer) -> Result<f64, EvalError> {
|
||||
classify_float(n.to_f64())
|
||||
classify_float(n.to_f64().value())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_r_to_f(r: &Rational) -> Result<f64, EvalError> {
|
||||
classify_float(r.to_f64())
|
||||
classify_float(r.to_f64().value())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -548,8 +549,8 @@ impl PartialEq for Number {
|
||||
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).eq(&n2),
|
||||
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.eq(&OrderedFloat(n2.get_num() as f64)),
|
||||
(&Number::Integer(ref n1), &Number::Integer(ref n2)) => n1.eq(n2),
|
||||
(&Number::Integer(ref n1), Number::Float(n2)) => OrderedFloat(n1.to_f64()).eq(n2),
|
||||
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64())),
|
||||
(&Number::Integer(ref n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(n2),
|
||||
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
|
||||
(&Number::Integer(ref n1), &Number::Rational(ref n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
@@ -570,8 +571,8 @@ impl PartialEq for Number {
|
||||
&**n1 == &**n2
|
||||
}
|
||||
}
|
||||
(&Number::Rational(ref n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64()).eq(&n2),
|
||||
(&Number::Float(n1), &Number::Rational(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64())),
|
||||
(&Number::Rational(ref n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(&n2),
|
||||
(&Number::Float(n1), &Number::Rational(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
|
||||
(&Number::Float(f1), &Number::Float(f2)) => f1.eq(&f2),
|
||||
(&Number::Rational(ref r1), &Number::Rational(ref r2)) => r1.eq(&r2),
|
||||
}
|
||||
@@ -639,8 +640,8 @@ impl Ord for Number {
|
||||
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).cmp(&n2),
|
||||
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.cmp(&OrderedFloat(n2.get_num() as f64)),
|
||||
(&Number::Integer(n1), &Number::Integer(n2)) => (*n1).cmp(&*n2),
|
||||
(&Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64()).cmp(n2),
|
||||
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.cmp(&OrderedFloat(n2.to_f64())),
|
||||
(&Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).cmp(n2),
|
||||
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.cmp(&OrderedFloat(n2.to_f64().value())),
|
||||
(&Number::Integer(n1), &Number::Rational(n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
@@ -661,8 +662,8 @@ impl Ord for Number {
|
||||
(&*n1).partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||
}
|
||||
}
|
||||
(&Number::Rational(n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64()).cmp(&n2),
|
||||
(&Number::Float(n1), &Number::Rational(n2)) => n1.cmp(&OrderedFloat(n2.to_f64())),
|
||||
(&Number::Rational(n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).cmp(&n2),
|
||||
(&Number::Float(n1), &Number::Rational(n2)) => n1.cmp(&OrderedFloat(n2.to_f64().value())),
|
||||
(&Number::Float(f1), &Number::Float(f2)) => f1.cmp(&f2),
|
||||
(&Number::Rational(r1), &Number::Rational(r2)) => (*r1).cmp(&*r2),
|
||||
}
|
||||
@@ -691,7 +692,7 @@ impl TryFrom<HeapCellValue> for Number {
|
||||
(HeapCellValueTag::F64, n) => {
|
||||
Ok(Number::Float(*n))
|
||||
}
|
||||
(HeapCellValueTag::Fixnum, n) => {
|
||||
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
|
||||
Ok(Number::Fixnum(n))
|
||||
}
|
||||
_ => {
|
||||
@@ -703,7 +704,7 @@ impl TryFrom<HeapCellValue> for Number {
|
||||
|
||||
// Computes n ^ power. Ignores the sign of power.
|
||||
pub(crate) fn binary_pow(mut n: Integer, power: &Integer) -> Integer {
|
||||
let mut power = Integer::from(power.abs_ref());
|
||||
let mut power = Integer::from(power.abs());
|
||||
|
||||
if power == 0 {
|
||||
return Integer::from(1);
|
||||
@@ -716,7 +717,7 @@ pub(crate) fn binary_pow(mut n: Integer, power: &Integer) -> Integer {
|
||||
oddand *= &n;
|
||||
}
|
||||
|
||||
n.pow_assign(2);
|
||||
n = n.pow(2);
|
||||
power >>= 1;
|
||||
}
|
||||
|
||||
|
||||
+58
-23
@@ -37,42 +37,61 @@ impl From<bool> for Atom {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use std::cell::RefCell;
|
||||
|
||||
const ATOM_TABLE_INIT_SIZE: usize = 1 << 16;
|
||||
const ATOM_TABLE_ALIGN: usize = 8;
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
static ATOM_TABLE_BUF_BASE: RefCell<*const u8> = RefCell::new(ptr::null_mut());
|
||||
static ATOM_TABLE_BUF_BASE: std::cell::RefCell<*const u8> = std::cell::RefCell::new(ptr::null_mut());
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
static mut ATOM_TABLE_BUF_BASE: *const u8 = ptr::null_mut();
|
||||
static ATOM_TABLE_BUF_BASE: std::sync::atomic::AtomicPtr<u8> =
|
||||
std::sync::atomic::AtomicPtr::new(ptr::null_mut());
|
||||
|
||||
fn set_atom_tbl_buf_base(old_ptr: *const u8, new_ptr: *const u8) -> Result<(), *const u8> {
|
||||
#[cfg(test)]
|
||||
fn set_atom_tbl_buf_base(ptr: *const u8) {
|
||||
{
|
||||
ATOM_TABLE_BUF_BASE.with(|atom_table_buf_base| {
|
||||
*atom_table_buf_base.borrow_mut() = ptr;
|
||||
});
|
||||
let mut borrow = atom_table_buf_base.borrow_mut();
|
||||
if *borrow != old_ptr {
|
||||
Err(*borrow)
|
||||
} else {
|
||||
*borrow = new_ptr;
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
ATOM_TABLE_BUF_BASE
|
||||
.compare_exchange(
|
||||
old_ptr.cast_mut(),
|
||||
new_ptr.cast_mut(),
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
)
|
||||
.map_err(|ptr| ptr.cast_const())
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn get_atom_tbl_buf_base() -> *const u8 {
|
||||
#[cfg(test)]
|
||||
{
|
||||
ATOM_TABLE_BUF_BASE.with(|atom_table_buf_base| *atom_table_buf_base.borrow())
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
fn set_atom_tbl_buf_base(ptr: *const u8) {
|
||||
unsafe {
|
||||
ATOM_TABLE_BUF_BASE = ptr;
|
||||
{
|
||||
ATOM_TABLE_BUF_BASE.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub(crate) fn get_atom_tbl_buf_base() -> *const u8 {
|
||||
unsafe { ATOM_TABLE_BUF_BASE }
|
||||
#[test]
|
||||
#[should_panic(expected = "Overwriting atom table base pointer")]
|
||||
fn atomtable_is_not_concurrency_safe() {
|
||||
let _table_a = AtomTable::new();
|
||||
let _table_b = AtomTable::new();
|
||||
}
|
||||
|
||||
impl RawBlockTraits for AtomTable {
|
||||
@@ -239,8 +258,17 @@ pub struct AtomTable {
|
||||
pub table: IndexSet<Atom>,
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn atom_table_base_pointer_mismatch(expected: *const u8, got: *const u8) -> ! {
|
||||
assert_eq!(expected, got, "Overwriting atom table base pointer, expected old value to be {expected:p}, but found {got:p}");
|
||||
unreachable!("This should only be called in a case of a mismatch as such the assert_eq should have failed!")
|
||||
}
|
||||
|
||||
impl Drop for AtomTable {
|
||||
fn drop(&mut self) {
|
||||
if let Err(got) = set_atom_tbl_buf_base(self.block.base, ptr::null()) {
|
||||
atom_table_base_pointer_mismatch(self.block.base, got);
|
||||
}
|
||||
self.block.deallocate();
|
||||
}
|
||||
}
|
||||
@@ -248,13 +276,17 @@ impl Drop for AtomTable {
|
||||
impl AtomTable {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
let table = Self {
|
||||
block: RawBlock::new(),
|
||||
table: IndexSet::new(),
|
||||
};
|
||||
let mut block = RawBlock::new();
|
||||
|
||||
set_atom_tbl_buf_base(table.block.base);
|
||||
table
|
||||
if let Err(got) = set_atom_tbl_buf_base(ptr::null(), block.base) {
|
||||
block.deallocate();
|
||||
atom_table_base_pointer_mismatch(ptr::null(), got);
|
||||
}
|
||||
|
||||
Self {
|
||||
block,
|
||||
table: IndexSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -289,8 +321,11 @@ impl AtomTable {
|
||||
ptr = self.block.alloc(size);
|
||||
|
||||
if ptr.is_null() {
|
||||
let old_base = self.block.base;
|
||||
self.block.grow();
|
||||
set_atom_tbl_buf_base(self.block.base);
|
||||
if let Err(got) = set_atom_tbl_buf_base(old_base, self.block.base) {
|
||||
atom_table_base_pointer_mismatch(old_base, got);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
fn main() {
|
||||
fn main() -> std::process::ExitCode {
|
||||
use std::sync::atomic::Ordering;
|
||||
use scryer_prolog::*;
|
||||
use scryer_prolog::atom_table::Atom;
|
||||
@@ -14,6 +14,6 @@ fn main() {
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = machine::Machine::new(Default::default());
|
||||
wam.run_top_level(atom!("$toplevel"), (atom!("$repl"), 1));
|
||||
});
|
||||
wam.run_top_level(atom!("$toplevel"), (atom!("$repl"), 1))
|
||||
})
|
||||
}
|
||||
|
||||
+428
-491
File diff suppressed because it is too large
Load Diff
+546
-108
@@ -1,42 +1,252 @@
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::allocator::*;
|
||||
use crate::fixtures::*;
|
||||
use crate::codegen::SubsumedBranchHits;
|
||||
use crate::forms::Level;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::machine::disjuncts::VarData;
|
||||
use crate::parser::ast::*;
|
||||
use crate::targets::CompilationTarget;
|
||||
|
||||
use crate::temp_v;
|
||||
use crate::targets::*;
|
||||
use crate::variable_records::*;
|
||||
|
||||
use bit_set::*;
|
||||
use bitvec::prelude::*;
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::BTreeSet;
|
||||
use std::rc::Rc;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub type BranchHits = IndexMap<usize, BitVec, FxBuildHasher>; // key: var_num, value: branch arm occurrences.
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BranchOccurrences {
|
||||
pub hits: BranchHits,
|
||||
pub shallow_safety: BitSet<usize>, // unset means safe, set means unsafe (after the branch merge)
|
||||
pub deep_safety: BitSet<usize>,
|
||||
pub num_branches: usize,
|
||||
pub current_branch: usize,
|
||||
pub subsumed_hits: SubsumedBranchHits,
|
||||
}
|
||||
|
||||
impl BranchOccurrences {
|
||||
fn new(num_branches: usize) -> Self {
|
||||
Self {
|
||||
hits: BranchHits::with_hasher(FxBuildHasher::default()),
|
||||
shallow_safety: BitSet::default(),
|
||||
deep_safety: BitSet::default(),
|
||||
num_branches,
|
||||
current_branch: 0,
|
||||
subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BranchStack {
|
||||
stack: Vec<BranchOccurrences>,
|
||||
}
|
||||
|
||||
impl Deref for BranchStack {
|
||||
type Target = Vec<BranchOccurrences>;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.stack
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BranchStack {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.stack
|
||||
}
|
||||
}
|
||||
|
||||
impl BranchStack {
|
||||
fn branch_subsumes(&self, branch: &BranchDesignator, sub_branch: &BranchDesignator) -> bool {
|
||||
if branch.branch_stack_num < sub_branch.branch_stack_num {
|
||||
if branch.branch_stack_num == 0 {
|
||||
true
|
||||
} else {
|
||||
let idx = branch.branch_stack_num - 1;
|
||||
self[idx].current_branch == branch.branch_num
|
||||
}
|
||||
} else {
|
||||
branch == sub_branch
|
||||
}
|
||||
}
|
||||
|
||||
fn safety_unneeded_in_branch(&self, safety: &VarSafetyStatus, branch: &BranchDesignator) -> bool {
|
||||
match safety {
|
||||
VarSafetyStatus::Needed => false,
|
||||
VarSafetyStatus::LocallyUnneeded(planter_branch) =>
|
||||
self.branch_subsumes(planter_branch, branch),
|
||||
VarSafetyStatus::GloballyUnneeded => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
|
||||
if let Some(occurrences) = self.last_mut() {
|
||||
debug_assert!(occurrences.current_branch < occurrences.num_branches);
|
||||
|
||||
let num_branches = occurrences.num_branches;
|
||||
|
||||
let entry = occurrences.hits.entry(var_num)
|
||||
.or_insert_with(|| BitVec::repeat(false, num_branches));
|
||||
|
||||
entry.set(occurrences.current_branch, true);
|
||||
occurrences.subsumed_hits.insert(var_num);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_branch_stack(&mut self, num_branches: usize) {
|
||||
self.push(BranchOccurrences::new(num_branches));
|
||||
}
|
||||
|
||||
pub(crate) fn current_branch_designator(&self) -> BranchDesignator {
|
||||
let branch_stack_num = self.len();
|
||||
let branch_num = self.last()
|
||||
.map(|occurrences| occurrences.current_branch)
|
||||
.unwrap_or(0);
|
||||
|
||||
BranchDesignator { branch_stack_num, branch_num }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn incr_current_branch(&mut self) {
|
||||
let branch_occurrences = self.last_mut().unwrap();
|
||||
branch_occurrences.current_branch += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn drain_branches(&mut self, depth: usize) -> std::vec::Drain<BranchOccurrences> {
|
||||
let start_idx = self.len() - depth;
|
||||
self.drain(start_idx ..)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DebrayAllocator {
|
||||
bindings: IndexMap<Rc<String>, VarData, FxBuildHasher>,
|
||||
pub(crate) var_data: VarData, // var_data replaces bindings.
|
||||
pub(crate) branch_stack: BranchStack,
|
||||
pub(crate) in_tail_position: bool,
|
||||
arg_c: usize,
|
||||
temp_lb: usize,
|
||||
perm_lb: usize,
|
||||
arity: usize, // 0 if not at head.
|
||||
contents: IndexMap<usize, Rc<String>, FxBuildHasher>,
|
||||
in_use: BTreeSet<usize>,
|
||||
shallow_temp_mappings: IndexMap<usize, usize, FxBuildHasher>,
|
||||
in_use: BitSet<usize>, // deep and non-var allocations
|
||||
temp_free_list: Vec<usize>,
|
||||
perm_free_list: VecDeque<(usize, usize)>, // chunk_num, var_num
|
||||
}
|
||||
|
||||
impl DebrayAllocator {
|
||||
fn is_curr_arg_distinct_from(&self, var: &String) -> bool {
|
||||
match self.contents.get(&self.arg_c) {
|
||||
Some(t_var) if **t_var != *var => true,
|
||||
pub(crate) fn add_branch(&mut self) {
|
||||
let branch_designator = self.branch_stack.current_branch_designator();
|
||||
let subsumed_hits = {
|
||||
let branch_occurrences = self.branch_stack.last_mut().unwrap();
|
||||
|
||||
std::mem::replace(
|
||||
&mut branch_occurrences.subsumed_hits,
|
||||
SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
|
||||
)
|
||||
};
|
||||
|
||||
for var_num in subsumed_hits {
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, ref mut allocation) => {
|
||||
match allocation {
|
||||
PermVarAllocation::Done { shallow_safety, deep_safety, .. } => {
|
||||
if !self.branch_stack.safety_unneeded_in_branch(shallow_safety, &branch_designator) {
|
||||
let branch_occurrences = self.branch_stack.last_mut().unwrap();
|
||||
branch_occurrences.shallow_safety.insert(var_num);
|
||||
}
|
||||
|
||||
if !self.branch_stack.safety_unneeded_in_branch(deep_safety, &branch_designator) {
|
||||
let branch_occurrences = self.branch_stack.last_mut().unwrap();
|
||||
branch_occurrences.deep_safety.insert(var_num);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
*allocation = PermVarAllocation::Pending;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop_branch(&mut self, depth: usize, subsumed_hits: SubsumedBranchHits) {
|
||||
let removed_branches = self.branch_stack.drain_branches(depth);
|
||||
|
||||
let (deep_safety, shallow_safety) = removed_branches
|
||||
.into_iter()
|
||||
.fold((BitSet::default(), BitSet::default()),
|
||||
|(mut deep_safety, mut shallow_safety), branch_occurrences| {
|
||||
deep_safety.union_with(&branch_occurrences.deep_safety);
|
||||
shallow_safety.union_with(&branch_occurrences.shallow_safety);
|
||||
|
||||
(deep_safety, shallow_safety)
|
||||
});
|
||||
|
||||
let branch_designator = self.branch_stack.current_branch_designator();
|
||||
|
||||
let (deep_safety, shallow_safety) = match self.branch_stack.last_mut() {
|
||||
Some(latest_branch) => {
|
||||
latest_branch.deep_safety.union_with(&deep_safety);
|
||||
latest_branch.shallow_safety.union_with(&shallow_safety);
|
||||
|
||||
(&latest_branch.deep_safety, &latest_branch.shallow_safety)
|
||||
}
|
||||
None => (&deep_safety, &shallow_safety)
|
||||
};
|
||||
|
||||
for var_num in subsumed_hits.iter().cloned() {
|
||||
let running_count = self.var_data.records[var_num].running_count;
|
||||
let num_occurrences = self.var_data.records[var_num].num_occurrences;
|
||||
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, allocation) => {
|
||||
let shallow_safety = VarSafetyStatus::needed_if(
|
||||
shallow_safety.contains(var_num),
|
||||
branch_designator,
|
||||
);
|
||||
|
||||
let deep_safety = VarSafetyStatus::needed_if(
|
||||
deep_safety.contains(var_num),
|
||||
branch_designator,
|
||||
);
|
||||
|
||||
if running_count < num_occurrences {
|
||||
*allocation = PermVarAllocation::Done { shallow_safety, deep_safety };
|
||||
}
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
if self.branch_stack.len() > 0 {
|
||||
for var_num in subsumed_hits {
|
||||
self.branch_stack.add_branch_occurrence(var_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_curr_arg_distinct_from(&self, var_num: usize) -> bool {
|
||||
match self.shallow_temp_mappings.get(&self.arg_c).cloned() {
|
||||
Some(t_var) => t_var != var_num,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn occurs_shallowly_in_head(&self, var: &String, r: usize) -> bool {
|
||||
match self.bindings.get(var).unwrap() {
|
||||
&VarData::Temp(_, _, ref tvd) => tvd.use_set.contains(&(GenContext::Head, r)),
|
||||
fn occurs_shallowly_in_head(&self, var_num: usize, r: usize) -> bool {
|
||||
match &self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Temp { temp_var_data, term_loc: GenContext::Head, .. } => {
|
||||
temp_var_data.use_set.contains(&(GenContext::Head, r))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -44,13 +254,13 @@ impl DebrayAllocator {
|
||||
#[inline]
|
||||
fn is_in_use(&self, r: usize) -> bool {
|
||||
let in_use_range = r <= self.arity && r >= self.arg_c;
|
||||
in_use_range || self.in_use.contains(&r)
|
||||
in_use_range || self.in_use.contains(r)
|
||||
}
|
||||
|
||||
fn alloc_with_cr(&self, var: &String) -> usize {
|
||||
match self.bindings.get(var) {
|
||||
Some(&VarData::Temp(_, _, ref tvd)) => {
|
||||
for &(_, reg) in tvd.use_set.iter() {
|
||||
fn alloc_with_cr(&self, var_num: usize) -> usize {
|
||||
match &self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Temp { temp_var_data, .. } => {
|
||||
for &(_, reg) in temp_var_data.use_set.iter() {
|
||||
if !self.is_in_use(reg) {
|
||||
return reg;
|
||||
}
|
||||
@@ -60,7 +270,7 @@ impl DebrayAllocator {
|
||||
|
||||
for reg in self.temp_lb.. {
|
||||
if !self.is_in_use(reg) {
|
||||
if !tvd.no_use_set.contains(®) {
|
||||
if !temp_var_data.no_use_set.contains(reg) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
@@ -73,10 +283,10 @@ impl DebrayAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_with_ca(&self, var: &String) -> usize {
|
||||
match self.bindings.get(var) {
|
||||
Some(&VarData::Temp(_, _, ref tvd)) => {
|
||||
for &(_, reg) in tvd.use_set.iter() {
|
||||
fn alloc_with_ca(&self, var_num: usize) -> usize {
|
||||
match &self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Temp { temp_var_data, .. } => {
|
||||
for &(_, reg) in temp_var_data.use_set.iter() {
|
||||
if !self.is_in_use(reg) {
|
||||
return reg;
|
||||
}
|
||||
@@ -86,8 +296,8 @@ impl DebrayAllocator {
|
||||
|
||||
for reg in self.temp_lb.. {
|
||||
if !self.is_in_use(reg) {
|
||||
if !tvd.no_use_set.contains(®) {
|
||||
if !tvd.conflict_set.contains(®) {
|
||||
if !temp_var_data.no_use_set.contains(reg) {
|
||||
if !temp_var_data.conflict_set.contains(reg) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
@@ -101,22 +311,25 @@ impl DebrayAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_in_last_goal_hint(&self, chunk_num: usize) -> Option<(Rc<String>, usize)> {
|
||||
fn alloc_in_last_goal_hint(&self, chunk_num: usize) -> Option<(usize, usize)> {
|
||||
// we want to allocate a register to the k^{th} parameter, par_k.
|
||||
// par_k may not be a temporary variable.
|
||||
let k = self.arg_c;
|
||||
|
||||
match self.contents.get(&k) {
|
||||
match self.shallow_temp_mappings.get(&k).cloned() {
|
||||
Some(t_var) => {
|
||||
// suppose this branch fires. then t_var is a
|
||||
// temp. var. belonging to the current chunk.
|
||||
// consider its use set. T == par_k iff
|
||||
// (GenContext::Last(_), k) is in t_var.use_set.
|
||||
|
||||
let tvd = self.bindings.get(t_var).unwrap();
|
||||
if let &VarData::Temp(_, _, ref tvd) = tvd {
|
||||
if !tvd.use_set.contains(&(GenContext::Last(chunk_num), k)) {
|
||||
return Some((t_var.clone(), self.alloc_with_ca(t_var)));
|
||||
match &self.var_data.records[t_var].allocation {
|
||||
VarAlloc::Temp { temp_var_data, .. } => {
|
||||
if !temp_var_data.use_set.contains(&(GenContext::Last(chunk_num), k)) {
|
||||
return Some((t_var, self.alloc_with_ca(t_var)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,21 +342,21 @@ impl DebrayAllocator {
|
||||
fn evacuate_arg<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
chunk_num: usize,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
match self.alloc_in_last_goal_hint(chunk_num) {
|
||||
Some((var, r)) => {
|
||||
Some((var_num, r)) => {
|
||||
let k = self.arg_c;
|
||||
|
||||
if r != k {
|
||||
let r = RegType::Temp(r);
|
||||
|
||||
code.push(Target::move_to_register(r, k));
|
||||
code.push_back(Target::move_to_register(r, k));
|
||||
|
||||
self.contents.swap_remove(&k);
|
||||
self.contents.insert(r.reg_num(), var.clone());
|
||||
self.shallow_temp_mappings.swap_remove(&k);
|
||||
self.shallow_temp_mappings.insert(r.reg_num(), var_num);
|
||||
|
||||
self.record_register(var, r);
|
||||
self.var_data.records[var_num].allocation.set_register(r.reg_num());
|
||||
self.in_use.insert(r.reg_num());
|
||||
}
|
||||
}
|
||||
@@ -153,27 +366,27 @@ impl DebrayAllocator {
|
||||
|
||||
fn alloc_reg_to_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var: &String,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
term_loc: GenContext,
|
||||
target: &mut Vec<Instruction>,
|
||||
target: &mut CodeDeque,
|
||||
) -> usize {
|
||||
match term_loc {
|
||||
GenContext::Head => {
|
||||
if let Level::Shallow = lvl {
|
||||
self.evacuate_arg::<Target>(0, target);
|
||||
self.alloc_with_cr(var)
|
||||
self.alloc_with_cr(var_num)
|
||||
} else {
|
||||
self.alloc_with_ca(var)
|
||||
self.alloc_with_ca(var_num)
|
||||
}
|
||||
}
|
||||
GenContext::Mid(_) => self.alloc_with_ca(var),
|
||||
GenContext::Mid(_) => self.alloc_with_ca(var_num),
|
||||
GenContext::Last(chunk_num) => {
|
||||
if let Level::Shallow = lvl {
|
||||
self.evacuate_arg::<Target>(chunk_num, target);
|
||||
self.alloc_with_cr(var)
|
||||
self.alloc_with_cr(var_num)
|
||||
} else {
|
||||
self.alloc_with_ca(var)
|
||||
self.alloc_with_ca(var_num)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,38 +395,238 @@ impl DebrayAllocator {
|
||||
fn alloc_reg_to_non_var(&mut self) -> usize {
|
||||
let mut final_index = 0;
|
||||
|
||||
while let Some(r) = self.temp_free_list.pop() {
|
||||
if !self.is_in_use(r) {
|
||||
self.in_use.insert(r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
for index in self.temp_lb.. {
|
||||
if !self.in_use.contains(&index) {
|
||||
if !self.in_use.contains(index) {
|
||||
final_index = index;
|
||||
self.in_use.insert(final_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.in_use.insert(final_index);
|
||||
self.temp_lb = final_index + 1;
|
||||
final_index
|
||||
}
|
||||
|
||||
fn in_place(&self, var: &String, term_loc: GenContext, r: RegType, k: usize) -> bool {
|
||||
fn in_place(&self, var_num: usize, term_loc: GenContext, r: RegType, k: usize) -> bool {
|
||||
match term_loc {
|
||||
GenContext::Head if !r.is_perm() => r.reg_num() == k,
|
||||
_ => match self.bindings().get(var).unwrap() {
|
||||
&VarData::Temp(_, o, _) if r.reg_num() == k => o == k,
|
||||
_ => false,
|
||||
_ => {
|
||||
match &self.var_data.records[var_num].allocation {
|
||||
&VarAlloc::Temp { temp_reg, .. } if r.reg_num() == k =>
|
||||
temp_reg == k,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_perm_var(&mut self, var_num: usize, chunk_num: usize) -> usize {
|
||||
let p = if let Some(p) = self.pop_free_perm(chunk_num) {
|
||||
p
|
||||
} else {
|
||||
let p = self.perm_lb;
|
||||
self.perm_lb += 1;
|
||||
|
||||
p
|
||||
};
|
||||
|
||||
self.var_data.records[var_num].allocation = VarAlloc::Perm(p, PermVarAllocation::done());
|
||||
p
|
||||
}
|
||||
|
||||
pub(crate) fn add_reg_to_free_list(&mut self, r: RegType) {
|
||||
if let RegType::Temp(r) = r {
|
||||
self.in_use.remove(r);
|
||||
self.temp_free_list.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_free_list(&mut self) {
|
||||
self.temp_free_list.clear();
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_binding(&self, var_num: usize) -> RegType {
|
||||
self.var_data.records[var_num].allocation.as_reg_type()
|
||||
}
|
||||
|
||||
pub fn num_perm_vars(&self) -> usize {
|
||||
self.perm_lb - 1
|
||||
}
|
||||
|
||||
pub fn increment_running_count(&mut self, var_num: usize) {
|
||||
self.var_data.records[var_num].running_count += 1;
|
||||
}
|
||||
|
||||
fn add_perm_to_free_list(&mut self, chunk_num: usize, var_num: usize) {
|
||||
match &self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(..) => {
|
||||
self.perm_free_list.push_back((chunk_num, var_num));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_free_perm(&mut self, chunk_num: usize) -> Option<usize> {
|
||||
while let Some((perm_chunk_num, var_num)) = self.perm_free_list.front().cloned() {
|
||||
if chunk_num > perm_chunk_num {
|
||||
self.perm_free_list.pop_front();
|
||||
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(p, PermVarAllocation::Pending) if *p > 0 => {
|
||||
return Some(std::mem::replace(p, 0));
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn free_var(&mut self, chunk_num: usize, var_num: usize) {
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, allocation) => {
|
||||
*allocation = PermVarAllocation::Pending;
|
||||
self.add_perm_to_free_list(chunk_num, var_num);
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mark_safe_var_unconditionally(&mut self, var_num: usize) {
|
||||
let branch_designator = self.branch_stack.current_branch_designator();
|
||||
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, shallow_safety, .. }) => {
|
||||
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
}
|
||||
VarAlloc::Temp { safety, .. } => {
|
||||
*safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_safe_var(&mut self, var_num: usize, lvl: Level, term_loc: GenContext) {
|
||||
let branch_designator = self.branch_stack.current_branch_designator();
|
||||
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, shallow_safety, .. }) => {
|
||||
// GetVariable in head chunk is considered safe.
|
||||
if lvl == Level::Deep {
|
||||
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
} else if term_loc == GenContext::Head {
|
||||
*shallow_safety = VarSafetyStatus::GloballyUnneeded;
|
||||
} else {
|
||||
if let Some(temp_var_num) = self.shallow_temp_mappings.get(&self.arg_c).cloned() {
|
||||
match &mut self.var_data.records[temp_var_num].allocation {
|
||||
VarAlloc::Temp { ref mut to_perm_var_num, .. } => {
|
||||
*to_perm_var_num = Some(var_num);
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VarAlloc::Temp { ref mut safety, .. } => {
|
||||
*safety = VarSafetyStatus::GloballyUnneeded;
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn argument_to_value<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
r: RegType,
|
||||
arg_c: usize,
|
||||
) -> Instruction {
|
||||
let branch_designator = self.branch_stack.current_branch_designator();
|
||||
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, PermVarAllocation::Done { ref mut shallow_safety, .. }) => {
|
||||
if !self.in_tail_position || self.branch_stack.safety_unneeded_in_branch(shallow_safety, &branch_designator) {
|
||||
Target::argument_to_value(r, arg_c)
|
||||
} else {
|
||||
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
Target::unsafe_argument_to_value(r, arg_c)
|
||||
}
|
||||
}
|
||||
VarAlloc::Temp { ref mut safety, .. } => {
|
||||
if self.branch_stack.safety_unneeded_in_branch(safety, &branch_designator) {
|
||||
Target::argument_to_value(r, arg_c)
|
||||
} else {
|
||||
*safety = VarSafetyStatus::GloballyUnneeded;
|
||||
Target::unsafe_argument_to_value(r, arg_c)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subterm_to_value<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
r: RegType,
|
||||
) -> Instruction {
|
||||
let branch_designator = self.branch_stack.current_branch_designator();
|
||||
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, PermVarAllocation::Done { ref mut deep_safety, .. }) => {
|
||||
if self.branch_stack.safety_unneeded_in_branch(deep_safety, &branch_designator) {
|
||||
Target::subterm_to_value(r)
|
||||
} else {
|
||||
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
Target::unsafe_subterm_to_value(r)
|
||||
}
|
||||
}
|
||||
VarAlloc::Temp { ref mut safety, .. } => {
|
||||
if self.branch_stack.safety_unneeded_in_branch(safety, &branch_designator) {
|
||||
Target::subterm_to_value(r)
|
||||
} else {
|
||||
*safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
Target::unsafe_subterm_to_value(r)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for DebrayAllocator {
|
||||
fn new() -> DebrayAllocator {
|
||||
DebrayAllocator {
|
||||
Self {
|
||||
var_data: VarData::default(),
|
||||
in_tail_position: false,
|
||||
arity: 0,
|
||||
arg_c: 1,
|
||||
temp_lb: 1,
|
||||
bindings: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
contents: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
in_use: BTreeSet::new(),
|
||||
perm_lb: 1,
|
||||
shallow_temp_mappings: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
in_use: BitSet::default(),
|
||||
temp_free_list: vec![],
|
||||
perm_free_list: VecDeque::new(),
|
||||
branch_stack: BranchStack { stack: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,12 +634,12 @@ impl Allocator for DebrayAllocator {
|
||||
&mut self,
|
||||
lvl: Level,
|
||||
term_loc: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
let r = RegType::Temp(self.alloc_reg_to_non_var());
|
||||
|
||||
match lvl {
|
||||
Level::Deep => code.push(Target::subterm_to_variable(r)),
|
||||
Level::Deep => code.push_back(Target::subterm_to_variable(r)),
|
||||
Level::Root | Level::Shallow => {
|
||||
let k = self.arg_c;
|
||||
|
||||
@@ -236,7 +649,7 @@ impl Allocator for DebrayAllocator {
|
||||
|
||||
self.arg_c += 1;
|
||||
|
||||
code.push(Target::argument_to_variable(r, k));
|
||||
code.push_back(Target::argument_to_variable(r, k));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -246,7 +659,7 @@ impl Allocator for DebrayAllocator {
|
||||
lvl: Level,
|
||||
term_loc: GenContext,
|
||||
cell: &'a Cell<RegType>,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
let r = cell.get();
|
||||
|
||||
@@ -273,39 +686,49 @@ impl Allocator for DebrayAllocator {
|
||||
|
||||
fn mark_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var: Rc<String>,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
let (r, is_new_var) = match self.get(var.clone()) {
|
||||
let (r, is_new_var) = match self.get_binding(var_num) {
|
||||
RegType::Temp(0) => {
|
||||
// here, r is temporary *and* unassigned.
|
||||
let o = self.alloc_reg_to_var::<Target>(&var, lvl, term_loc, code);
|
||||
let o = self.alloc_reg_to_var::<Target>(var_num, lvl, term_loc, code);
|
||||
cell.set(VarReg::Norm(RegType::Temp(o)));
|
||||
|
||||
(RegType::Temp(o), true)
|
||||
}
|
||||
RegType::Perm(0) => {
|
||||
let pr = cell.get().norm();
|
||||
self.record_register(var.clone(), pr);
|
||||
let p = self.alloc_perm_var(var_num, term_loc.chunk_num());
|
||||
(RegType::Perm(p), true)
|
||||
}
|
||||
r @ RegType::Perm(_) => {
|
||||
let is_new_var = match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, allocation) => if allocation.pending() {
|
||||
*allocation = PermVarAllocation::done();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
(pr, true)
|
||||
(r, is_new_var)
|
||||
}
|
||||
r => (r, false),
|
||||
};
|
||||
|
||||
self.mark_reserved_var::<Target>(var, lvl, cell, term_loc, code, r, is_new_var);
|
||||
self.mark_reserved_var::<Target>(var_num, lvl, cell, term_loc, code, r, is_new_var);
|
||||
}
|
||||
|
||||
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var: Rc<String>,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
r: RegType,
|
||||
is_new_var: bool,
|
||||
) {
|
||||
@@ -313,84 +736,99 @@ impl Allocator for DebrayAllocator {
|
||||
Level::Root | Level::Shallow => {
|
||||
let k = self.arg_c;
|
||||
|
||||
if self.is_curr_arg_distinct_from(&var) {
|
||||
if self.is_curr_arg_distinct_from(var_num) {
|
||||
self.evacuate_arg::<Target>(term_loc.chunk_num(), code);
|
||||
}
|
||||
|
||||
self.arg_c += 1;
|
||||
|
||||
cell.set(VarReg::ArgAndNorm(r, k));
|
||||
|
||||
if !self.in_place(&var, term_loc, r, k) {
|
||||
if !self.in_place(var_num, term_loc, r, k) {
|
||||
if is_new_var {
|
||||
code.push(Target::argument_to_variable(r, k));
|
||||
self.mark_safe_var(var_num, lvl, term_loc);
|
||||
code.push_back(Target::argument_to_variable(r, k));
|
||||
} else {
|
||||
code.push(Target::argument_to_value(r, k));
|
||||
code.push_back(self.argument_to_value::<Target>(var_num, r, k));
|
||||
}
|
||||
}
|
||||
|
||||
self.arg_c += 1;
|
||||
}
|
||||
Level::Deep if is_new_var => {
|
||||
if let GenContext::Head = term_loc {
|
||||
if self.occurs_shallowly_in_head(&var, r.reg_num()) {
|
||||
code.push(Target::subterm_to_value(r));
|
||||
if self.occurs_shallowly_in_head(var_num, r.reg_num()) {
|
||||
code.push_back(self.subterm_to_value::<Target>(var_num, r));
|
||||
} else {
|
||||
code.push(Target::subterm_to_variable(r));
|
||||
self.mark_safe_var(var_num, lvl, term_loc);
|
||||
code.push_back(Target::subterm_to_variable(r));
|
||||
}
|
||||
} else {
|
||||
code.push(Target::subterm_to_variable(r));
|
||||
self.mark_safe_var(var_num, lvl, term_loc);
|
||||
code.push_back(Target::subterm_to_variable(r));
|
||||
}
|
||||
}
|
||||
Level::Deep => code.push(Target::subterm_to_value(r)),
|
||||
};
|
||||
Level::Deep => code.push_back(self.subterm_to_value::<Target>(var_num, r)),
|
||||
}
|
||||
|
||||
let o = r.reg_num();
|
||||
|
||||
if !r.is_perm() {
|
||||
let o = r.reg_num();
|
||||
self.shallow_temp_mappings.insert(o, var_num);
|
||||
} else if r.is_perm() && is_new_var {
|
||||
self.branch_stack.add_branch_occurrence(var_num);
|
||||
}
|
||||
|
||||
self.contents.insert(o, var.clone());
|
||||
self.record_register(var.clone(), r);
|
||||
self.in_use.insert(o);
|
||||
let record = &mut self.var_data.records[var_num];
|
||||
|
||||
record.allocation.set_register(o);
|
||||
|
||||
if record.running_count < record.num_occurrences {
|
||||
record.running_count += 1;
|
||||
} else {
|
||||
self.free_var(term_loc.chunk_num(), var_num);
|
||||
}
|
||||
|
||||
self.in_use.insert(o);
|
||||
}
|
||||
|
||||
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType {
|
||||
match self.get_binding(var_num) {
|
||||
RegType::Perm(0) | RegType::Temp(0) => {
|
||||
RegType::Perm(self.alloc_perm_var(var_num, chunk_num))
|
||||
}
|
||||
r => r,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.contents.clear();
|
||||
self.perm_lb = 1;
|
||||
self.shallow_temp_mappings.clear();
|
||||
self.in_use.clear();
|
||||
self.temp_free_list.clear();
|
||||
}
|
||||
|
||||
fn reset_contents(&mut self) {
|
||||
self.contents.clear();
|
||||
self.in_use.clear();
|
||||
self.shallow_temp_mappings.clear();
|
||||
self.temp_free_list.clear();
|
||||
}
|
||||
|
||||
fn advance_arg(&mut self) {
|
||||
self.arg_c += 1;
|
||||
}
|
||||
|
||||
fn bindings(&self) -> &AllocVarDict {
|
||||
&self.bindings
|
||||
}
|
||||
|
||||
fn bindings_mut(&mut self) -> &mut AllocVarDict {
|
||||
&mut self.bindings
|
||||
}
|
||||
|
||||
fn take_bindings(self) -> AllocVarDict {
|
||||
self.bindings
|
||||
}
|
||||
|
||||
fn reset_at_head(&mut self, args: &Vec<Term>) {
|
||||
self.reset_arg(args.len());
|
||||
self.arity = args.len();
|
||||
|
||||
for (idx, arg) in args.iter().enumerate() {
|
||||
if let &Term::Var(_, ref var) = arg {
|
||||
let r = self.get(var.clone());
|
||||
let var_num = var.to_var_num().unwrap();
|
||||
let r = self.get_binding(var_num);
|
||||
|
||||
if !r.is_perm() && r.reg_num() == 0 {
|
||||
self.in_use.insert(idx + 1);
|
||||
self.contents.insert(idx + 1, var.clone());
|
||||
self.record_register(var.clone(), temp_v!(idx + 1));
|
||||
self.shallow_temp_mappings.insert(idx + 1, var_num);
|
||||
self.var_data.records[var_num].allocation.set_register(idx + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
/* How does FFI work?
|
||||
|
||||
Each WAM machine has a ForeignFunctionTable instance that contains a table of functions and structs.
|
||||
|
||||
Structs are defined via foreign_struct/2. Basic types are defined by libffi, but struct types need to
|
||||
be manually defined to get an ffi_type. Additionally, to recover structs from return arguments, we store
|
||||
fields and atom_fields, as a way to lookup the content of the struct (fields) and the nested structs (atom_fields).
|
||||
|
||||
Functions are defined via use_foreign_module/2. It opens a library and leaks the memory of the library,
|
||||
to prevent Rust freeing the memory. There's no way to recover that memory at the moment. We get a pointer for
|
||||
each function and we build a CIF for each one, with the input arguments and the return argument.
|
||||
|
||||
Exec happens via '$foreign_call', we find the function, we try to cast the values that we have to the definition
|
||||
of the function, we reserve memory for them and we build an array of pointers. To get the return argument, we
|
||||
reserve enough memory for the return and we build the Scryer values from them.
|
||||
|
||||
Structs are a bit tricky as they need to be aligned. For that, we reserve enough memory (libffi calculates that)
|
||||
and for each field: we add to the pointer until we're aligned to the next data type we're going to write, we write it,
|
||||
and finally we add the pointer the size of what we've written.
|
||||
*/
|
||||
|
||||
use crate::atom_table::Atom;
|
||||
|
||||
use std::alloc::{alloc, Layout};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::ffi::{CString, c_void};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use libffi::low::{ffi_cif, types, CodePtr, ffi_abi_FFI_DEFAULT_ABI, prep_cif, ffi_type, type_tag};
|
||||
use libloading::{Symbol, Library};
|
||||
|
||||
pub struct FunctionDefinition {
|
||||
pub name: String,
|
||||
pub return_value: Atom,
|
||||
pub args: Vec<Atom>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionImpl {
|
||||
cif: ffi_cif,
|
||||
args: Vec<*mut ffi_type>,
|
||||
code_ptr: CodePtr,
|
||||
return_struct_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ForeignFunctionTable {
|
||||
table: HashMap<String, FunctionImpl>,
|
||||
structs: HashMap<String, StructImpl>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StructImpl {
|
||||
ffi_type: ffi_type,
|
||||
fields: Vec<*mut ffi_type>,
|
||||
atom_fields: Vec<Atom>,
|
||||
}
|
||||
|
||||
struct PointerArgs {
|
||||
pointers: Vec<*mut c_void>,
|
||||
_memory: Vec<Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl ForeignFunctionTable {
|
||||
pub fn merge(&mut self, other: ForeignFunctionTable) {
|
||||
self.table.extend(other.table);
|
||||
}
|
||||
|
||||
pub fn define_struct(&mut self, name: &str, atom_fields: Vec<Atom>) {
|
||||
let mut fields: Vec<_> = atom_fields.iter().map(|x| self.map_type_ffi(&x)).collect();
|
||||
fields.push(std::ptr::null_mut::<ffi_type>());
|
||||
let mut struct_type: ffi_type = Default::default();
|
||||
struct_type.type_ = type_tag::STRUCT;
|
||||
struct_type.elements = fields.as_mut_ptr();
|
||||
self.structs.insert(name.to_string(), StructImpl { ffi_type: struct_type, fields, atom_fields});
|
||||
}
|
||||
|
||||
fn map_type_ffi(&mut self, source: &Atom) -> *mut ffi_type {
|
||||
unsafe {
|
||||
match source {
|
||||
atom!("sint64") => &mut types::sint64,
|
||||
atom!("sint32") => &mut types::sint32,
|
||||
atom!("sint16") => &mut types::sint16,
|
||||
atom!("sint8") => &mut types::sint8,
|
||||
atom!("uint64") => &mut types::uint64,
|
||||
atom!("uint32") => &mut types::uint32,
|
||||
atom!("uint16") => &mut types::uint16,
|
||||
atom!("uint8") => &mut types::uint8,
|
||||
atom!("bool") => &mut types::sint8,
|
||||
atom!("void") => &mut types::void,
|
||||
atom!("cstr") => &mut types::pointer,
|
||||
atom!("ptr") => &mut types::pointer,
|
||||
atom!("f32") => &mut types::float,
|
||||
atom!("f64") => &mut types::double,
|
||||
struct_name => {
|
||||
match self.structs.get_mut(struct_name.as_str()) {
|
||||
Some(ref mut struct_type) => {
|
||||
&mut struct_type.ffi_type
|
||||
},
|
||||
None => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_library(&mut self, library_name: &str, functions: &Vec<FunctionDefinition>) -> Result<(), Box<dyn Error>> {
|
||||
let mut ff_table: ForeignFunctionTable = Default::default();
|
||||
unsafe {
|
||||
let library = Library::new(library_name)?;
|
||||
for function in functions {
|
||||
let symbol_name: CString = CString::new(function.name.clone())?;
|
||||
let code_ptr: Symbol<*mut c_void> = library.get(&symbol_name.into_bytes_with_nul())?;
|
||||
let mut args: Vec<_> = function.args.iter().map(|x| self.map_type_ffi(&x)).collect();
|
||||
let mut cif: ffi_cif = Default::default();
|
||||
prep_cif(
|
||||
&mut cif,
|
||||
ffi_abi_FFI_DEFAULT_ABI,
|
||||
args.len(),
|
||||
self.map_type_ffi(&function.return_value),
|
||||
args.as_mut_ptr()
|
||||
).unwrap();
|
||||
|
||||
let return_struct_name = if (*self.map_type_ffi(&function.return_value)).type_ as u32 == libffi::raw::FFI_TYPE_STRUCT {
|
||||
Some(function.return_value.as_str().to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ff_table.table.insert(function.name.clone(), FunctionImpl {
|
||||
cif,
|
||||
args,
|
||||
code_ptr: CodePtr(code_ptr.into_raw().into_raw() as *mut _),
|
||||
return_struct_name,
|
||||
});
|
||||
}
|
||||
std::mem::forget(library);
|
||||
}
|
||||
self.merge(ff_table);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_pointer_args(args: &mut Vec<Value>, type_args: &Vec<*mut ffi_type>, structs_table: &mut HashMap<String, StructImpl>) -> Result<PointerArgs, FFIError> {
|
||||
let mut pointers = Vec::with_capacity(args.len());
|
||||
let mut _memory = Vec::new();
|
||||
for i in 0..args.len() {
|
||||
let field_type = type_args[i];
|
||||
unsafe {
|
||||
macro_rules! push_int {
|
||||
($type:ty) => {
|
||||
{
|
||||
let n: $type = <$type>::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?;
|
||||
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||
_memory.push(box_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (*field_type).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_UINT8 => push_int!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => push_int!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => push_int!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => push_int!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => push_int!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => push_int!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => push_int!(u64),
|
||||
libffi::raw::FFI_TYPE_SINT64 => push_int!(i64),
|
||||
libffi::raw::FFI_TYPE_FLOAT => {
|
||||
let n: f32 = args[i].as_float()? as f32;
|
||||
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||
_memory.push(box_value);
|
||||
},
|
||||
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||
let n: f64 = args[i].as_float()?;
|
||||
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||
_memory.push(box_value);
|
||||
},
|
||||
libffi::raw::FFI_TYPE_POINTER => {
|
||||
let ptr: *mut c_void = args[i].as_ptr()?;
|
||||
pointers.push(ptr);
|
||||
},
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let (mut ptr, _size, _align) = Self::build_struct(&mut args[i], structs_table)?;
|
||||
pointers.push(&mut *ptr as *mut _ as *mut c_void);
|
||||
_memory.push(ptr);
|
||||
},
|
||||
_ => return Err(FFIError::InvalidFFIType)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(PointerArgs {
|
||||
pointers,
|
||||
_memory
|
||||
})
|
||||
}
|
||||
|
||||
fn build_struct(arg: &mut Value, structs_table: &mut HashMap<String, StructImpl>) -> Result<(Box<dyn Any>, usize, usize), FFIError> {
|
||||
unsafe {
|
||||
match arg {
|
||||
Value::Struct(ref name, ref mut struct_args) => {
|
||||
if let Some(ref mut struct_type) = structs_table.clone().get_mut(name) {
|
||||
let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap();
|
||||
let align = struct_type.ffi_type.alignment as usize;
|
||||
let size = struct_type.ffi_type.size;
|
||||
let ptr = alloc(layout) as *mut c_void;
|
||||
let mut field_ptr = ptr;
|
||||
|
||||
for i in 0..(struct_type.fields.len()-1) {
|
||||
macro_rules! try_write_int {
|
||||
($type:ty) => {
|
||||
{
|
||||
field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>()));
|
||||
let n: $type = <$type>::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?;
|
||||
std::ptr::write(field_ptr as *mut $type, n);
|
||||
field_ptr = field_ptr.add(std::mem::size_of::<$type>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! write {
|
||||
($type:ty, $value:expr) => {
|
||||
{
|
||||
let data: $type = $value;
|
||||
std::ptr::write(field_ptr as *mut $type, data);
|
||||
field_ptr = field_ptr.add(align);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let field = struct_type.fields[i];
|
||||
match (*field).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_UINT8 => try_write_int!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => try_write_int!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => try_write_int!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => try_write_int!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => try_write_int!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => try_write_int!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => try_write_int!(u64),
|
||||
libffi::raw::FFI_TYPE_SINT64 => try_write_int!(i64),
|
||||
libffi::raw::FFI_TYPE_POINTER => write!(*mut c_void, struct_args[i].as_ptr()?),
|
||||
libffi::raw::FFI_TYPE_FLOAT => write!(f32, struct_args[i].as_float()? as f32),
|
||||
libffi::raw::FFI_TYPE_DOUBLE => write!(f64, struct_args[i].as_float()?),
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let (struct_ptr, struct_size, struct_align) = Self::build_struct(&mut struct_args[i], structs_table)?;
|
||||
field_ptr = field_ptr.add(field_ptr.align_offset(struct_align));
|
||||
|
||||
std::ptr::copy(& *struct_ptr as *const _ as *const c_void, field_ptr as *mut c_void, struct_size);
|
||||
field_ptr = field_ptr.add(struct_size);
|
||||
},
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok((Box::from_raw(ptr), size, align));
|
||||
} else {
|
||||
return Err(FFIError::InvalidStructName);
|
||||
}
|
||||
}
|
||||
_ => return Err(FFIError::ValueCast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(&mut self, name: &str, mut args: Vec<Value>) -> Result<Value, FFIError> {
|
||||
let function_impl = self.table.get_mut(name).ok_or(FFIError::FunctionNotFound)?;
|
||||
let mut pointer_args = Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs)?;
|
||||
|
||||
return unsafe {
|
||||
macro_rules! call_and_return {
|
||||
($type:ty) => {
|
||||
{
|
||||
let mut n: Box<u8> = Box::new(0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||
);
|
||||
Ok(Value::Int(i64::from(*n)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (*function_impl.cif.rtype).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_VOID => call_and_return!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT8 => call_and_return!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => call_and_return!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => call_and_return!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => call_and_return!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => call_and_return!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => call_and_return!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => {
|
||||
let mut n: Box<u64> = Box::new(0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||
);
|
||||
Ok(Value::Int(i64::try_from(*n).map_err(|_| FFIError::ValueDontFit)?))
|
||||
},
|
||||
libffi::raw::FFI_TYPE_SINT64 => call_and_return!(i64),
|
||||
libffi::raw::FFI_TYPE_POINTER => call_and_return!(*mut c_void),
|
||||
libffi::raw::FFI_TYPE_FLOAT => {
|
||||
let mut n: Box<f32> = Box::new(0.0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||
);
|
||||
Ok(Value::Float((*n).into()))
|
||||
},
|
||||
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||
let mut n: Box<f64> = Box::new(0.0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||
);
|
||||
Ok(Value::Float(*n))
|
||||
},
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let name = &function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?;
|
||||
let struct_type = self.structs.get(name).ok_or(FFIError::StructNotFound)?;
|
||||
let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap();
|
||||
let ptr = alloc(layout) as *mut c_void;
|
||||
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *ptr as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||
);
|
||||
let struct_val = self.read_struct(ptr, name, struct_type);
|
||||
drop(Box::from_raw(ptr));
|
||||
struct_val
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn read_struct(&self, ptr: *mut c_void, name: &str, struct_type: &StructImpl) -> Result<Value, FFIError> {
|
||||
unsafe {
|
||||
let mut returns = Vec::new();
|
||||
let mut field_ptr = ptr;
|
||||
|
||||
for i in 0..(struct_type.fields.len()-1) {
|
||||
let field = struct_type.fields[i];
|
||||
|
||||
macro_rules! read_and_push_int {
|
||||
($type:ty) => {
|
||||
{
|
||||
field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>()));
|
||||
let n = std::ptr::read(field_ptr as *mut $type);
|
||||
returns.push(Value::Int(i64::from(n)));
|
||||
field_ptr = field_ptr.add(std::mem::size_of::<$type>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (*field).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_UINT8 => read_and_push_int!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => read_and_push_int!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => read_and_push_int!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => read_and_push_int!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => read_and_push_int!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => read_and_push_int!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => {
|
||||
field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<u64>()));
|
||||
let n = std::ptr::read(field_ptr as *mut u64);
|
||||
returns.push(Value::Int(i64::try_from(n).map_err(|_| FFIError::ValueDontFit)?));
|
||||
field_ptr = field_ptr.add(std::mem::size_of::<u64>());
|
||||
},
|
||||
libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64),
|
||||
libffi::raw::FFI_TYPE_POINTER => read_and_push_int!(i64),
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let substruct = struct_type.atom_fields[i].as_str();
|
||||
let struct_type = self.structs.get(substruct).ok_or(FFIError::StructNotFound)?;
|
||||
field_ptr = field_ptr.add(field_ptr.align_offset(struct_type.ffi_type.alignment as usize));
|
||||
let struct_val = self.read_struct(field_ptr, substruct, struct_type);
|
||||
returns.push(struct_val?);
|
||||
field_ptr = field_ptr.add(struct_type.ffi_type.size);
|
||||
},
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Value::Struct(name.into(), returns))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
CString(CString),
|
||||
Struct(String, Vec<Value>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn as_int(&self) -> Result<i64, FFIError> {
|
||||
match self {
|
||||
Value::Int(n) => Ok(*n),
|
||||
_ => Err(FFIError::ValueCast),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_float(&self) -> Result<f64, FFIError> {
|
||||
match self {
|
||||
Value::Float(n) => Ok(*n),
|
||||
Value::Int(n) => Ok(*n as f64),
|
||||
_ => Err(FFIError::ValueCast),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&mut self) -> Result<*mut c_void, FFIError> {
|
||||
match self {
|
||||
Value::CString(ref mut cstr) => Ok(&mut *cstr as *mut _ as *mut c_void),
|
||||
Value::Int(n) => Ok(*n as *mut c_void),
|
||||
_ => Err(FFIError::ValueCast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FFIError {
|
||||
ValueCast,
|
||||
ValueDontFit,
|
||||
InvalidFFIType,
|
||||
InvalidStructName,
|
||||
FunctionNotFound,
|
||||
StructNotFound,
|
||||
}
|
||||
-320
@@ -1,320 +0,0 @@
|
||||
use crate::parser::ast::*;
|
||||
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::BTreeSet;
|
||||
use std::mem::swap;
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
// labeled with chunk numbers.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum VarStatus {
|
||||
Perm(usize),
|
||||
Temp(usize, TempVarData), // Perm(chunk_num) | Temp(chunk_num, _)
|
||||
}
|
||||
|
||||
pub(crate) type OccurrenceSet = BTreeSet<(GenContext, usize)>;
|
||||
|
||||
// Perm: 0 initially, a stack register once processed.
|
||||
// Temp: labeled with chunk_num and temp offset (unassigned if 0).
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum VarData {
|
||||
Perm(usize),
|
||||
Temp(usize, usize, TempVarData),
|
||||
}
|
||||
|
||||
impl VarData {
|
||||
pub(crate) fn as_reg_type(&self) -> RegType {
|
||||
match self {
|
||||
&VarData::Temp(_, r, _) => RegType::Temp(r),
|
||||
&VarData::Perm(r) => RegType::Perm(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TempVarData {
|
||||
pub(crate) last_term_arity: usize,
|
||||
pub(crate) use_set: OccurrenceSet,
|
||||
pub(crate) no_use_set: BTreeSet<usize>,
|
||||
pub(crate) conflict_set: BTreeSet<usize>,
|
||||
}
|
||||
|
||||
impl TempVarData {
|
||||
pub(crate) fn new(last_term_arity: usize) -> Self {
|
||||
TempVarData {
|
||||
last_term_arity: last_term_arity,
|
||||
use_set: BTreeSet::new(),
|
||||
no_use_set: BTreeSet::new(),
|
||||
conflict_set: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn uses_reg(&self, reg: usize) -> bool {
|
||||
for &(_, nreg) in self.use_set.iter() {
|
||||
if reg == nreg {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub(crate) fn populate_conflict_set(&mut self) {
|
||||
if self.last_term_arity > 0 {
|
||||
let arity = self.last_term_arity;
|
||||
let mut conflict_set: BTreeSet<usize> = (1..arity).collect();
|
||||
|
||||
for &(_, reg) in self.use_set.iter() {
|
||||
conflict_set.remove(®);
|
||||
}
|
||||
|
||||
self.conflict_set = conflict_set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type VariableFixture<'a> = (VarStatus, Vec<&'a Cell<VarReg>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct VariableFixtures<'a> {
|
||||
perm_vars: IndexMap<Rc<String>, VariableFixture<'a>>,
|
||||
last_chunk_temp_vars: IndexSet<Rc<String>>,
|
||||
}
|
||||
|
||||
impl<'a> VariableFixtures<'a> {
|
||||
pub(crate) fn new() -> Self {
|
||||
VariableFixtures {
|
||||
perm_vars: IndexMap::new(),
|
||||
last_chunk_temp_vars: IndexSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, var: Rc<String>, vs: VariableFixture<'a>) {
|
||||
self.perm_vars.insert(var, vs);
|
||||
}
|
||||
|
||||
pub(crate) fn insert_last_chunk_temp_var(&mut self, var: Rc<String>) {
|
||||
self.last_chunk_temp_vars.insert(var);
|
||||
}
|
||||
|
||||
// computes no_use and conflict sets for all temp vars.
|
||||
pub(crate) fn populate_restricting_sets(&mut self) {
|
||||
// three stages:
|
||||
// 1. move the use sets of each variable to a local IndexMap, use_set
|
||||
// (iterate mutably, swap mutable refs).
|
||||
// 2. drain use_set. For each use set of U, add into the
|
||||
// no-use sets of appropriate variables T =/= U.
|
||||
// 3. Move the use sets back to their original locations in the fixture.
|
||||
// Compute the conflict set of u.
|
||||
|
||||
// 1.
|
||||
let mut use_sets: IndexMap<Rc<String>, OccurrenceSet> = IndexMap::new();
|
||||
|
||||
for (var, &mut (ref mut var_status, _)) in self.iter_mut() {
|
||||
if let &mut VarStatus::Temp(_, ref mut var_data) = var_status {
|
||||
let mut use_set = OccurrenceSet::new();
|
||||
|
||||
swap(&mut var_data.use_set, &mut use_set);
|
||||
use_sets.insert((*var).clone(), use_set);
|
||||
}
|
||||
}
|
||||
|
||||
for (u, use_set) in use_sets.drain(..) {
|
||||
// 2.
|
||||
for &(term_loc, reg) in use_set.iter() {
|
||||
if let GenContext::Last(cn_u) = term_loc {
|
||||
for (ref t, &mut (ref mut var_status, _)) in self.iter_mut() {
|
||||
if let &mut VarStatus::Temp(cn_t, ref mut t_data) = var_status {
|
||||
if cn_u == cn_t && *u != ***t {
|
||||
if !t_data.uses_reg(reg) {
|
||||
t_data.no_use_set.insert(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3.
|
||||
match self.get_mut(u).unwrap() {
|
||||
&mut (VarStatus::Temp(_, ref mut u_data), _) => {
|
||||
u_data.use_set = use_set;
|
||||
u_data.populate_conflict_set();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, u: Rc<String>) -> Option<&mut VariableFixture<'a>> {
|
||||
self.perm_vars.get_mut(&u)
|
||||
}
|
||||
|
||||
fn iter_mut(&mut self) -> indexmap::map::IterMut<Rc<String>, VariableFixture<'a>> {
|
||||
self.perm_vars.iter_mut()
|
||||
}
|
||||
|
||||
fn record_temp_info(&mut self, tvd: &mut TempVarData, arg_c: usize, term_loc: GenContext) {
|
||||
match term_loc {
|
||||
GenContext::Head | GenContext::Last(_) => {
|
||||
tvd.use_set.insert((term_loc, arg_c));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn vars_above_threshold(&self, index: usize) -> usize {
|
||||
let mut var_count = 0;
|
||||
|
||||
for &(ref var_status, _) in self.values() {
|
||||
if let &VarStatus::Perm(i) = var_status {
|
||||
if i > index {
|
||||
var_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var_count
|
||||
}
|
||||
|
||||
pub(crate) fn mark_vars_in_chunk<I>(&mut self, iter: I, lt_arity: usize, term_loc: GenContext)
|
||||
where
|
||||
I: Iterator<Item = TermRef<'a>>,
|
||||
{
|
||||
let chunk_num = term_loc.chunk_num();
|
||||
let mut arg_c = 1;
|
||||
|
||||
for term_ref in iter {
|
||||
if let &TermRef::Var(lvl, cell, ref var) = &term_ref {
|
||||
let mut status = self.perm_vars.swap_remove(var).unwrap_or((
|
||||
VarStatus::Temp(chunk_num, TempVarData::new(lt_arity)),
|
||||
Vec::new(),
|
||||
));
|
||||
|
||||
status.1.push(cell);
|
||||
|
||||
match status.0 {
|
||||
VarStatus::Temp(cn, ref mut tvd) if cn == chunk_num => {
|
||||
if let Level::Shallow = lvl {
|
||||
self.record_temp_info(tvd, arg_c, term_loc);
|
||||
}
|
||||
}
|
||||
_ => status.0 = VarStatus::Perm(chunk_num),
|
||||
};
|
||||
|
||||
self.perm_vars.insert(var.clone(), status);
|
||||
}
|
||||
|
||||
if let Level::Shallow = term_ref.level() {
|
||||
arg_c += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_iter(self) -> indexmap::map::IntoIter<Rc<String>, VariableFixture<'a>> {
|
||||
self.perm_vars.into_iter()
|
||||
}
|
||||
|
||||
fn values(&self) -> indexmap::map::Values<Rc<String>, VariableFixture<'a>> {
|
||||
self.perm_vars.values()
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> usize {
|
||||
self.perm_vars.len()
|
||||
}
|
||||
|
||||
pub(crate) fn set_perm_vals(&self, has_deep_cuts: bool) {
|
||||
let mut values_vec: Vec<_> = self
|
||||
.values()
|
||||
.filter_map(|ref v| match &v.0 {
|
||||
&VarStatus::Perm(i) => Some((i, &v.1)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
values_vec.sort_by_key(|ref v| v.0);
|
||||
|
||||
let offset = has_deep_cuts as usize;
|
||||
|
||||
for (i, (_, cells)) in values_vec.into_iter().rev().enumerate() {
|
||||
for cell in cells {
|
||||
cell.set(VarReg::Norm(RegType::Perm(i + 1 + offset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnsafeVarMarker {
|
||||
pub(crate) unsafe_vars: IndexMap<RegType, usize>,
|
||||
pub(crate) safe_vars: IndexSet<RegType>,
|
||||
}
|
||||
|
||||
impl UnsafeVarMarker {
|
||||
pub(crate) fn new() -> Self {
|
||||
UnsafeVarMarker {
|
||||
unsafe_vars: IndexMap::new(),
|
||||
safe_vars: IndexSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_safe_vars(safe_vars: IndexSet<RegType>) -> Self {
|
||||
UnsafeVarMarker {
|
||||
unsafe_vars: IndexMap::new(),
|
||||
safe_vars,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mark_safe_vars(&mut self, query_instr: &Instruction) -> bool {
|
||||
match query_instr {
|
||||
&Instruction::PutVariable(r @ RegType::Temp(_), _) |
|
||||
&Instruction::SetVariable(r) => {
|
||||
self.safe_vars.insert(r);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mark_phase(&mut self, query_instr: &Instruction, phase: usize) {
|
||||
match query_instr {
|
||||
&Instruction::PutValue(r @ RegType::Perm(_), _) |
|
||||
&Instruction::SetValue(r) => {
|
||||
let p = self.unsafe_vars.entry(r).or_insert(0);
|
||||
*p = phase;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mark_unsafe_vars(&mut self, query_instr: &mut Instruction, phase: usize) {
|
||||
match query_instr {
|
||||
&mut Instruction::PutValue(RegType::Perm(i), arg) => {
|
||||
if let Some(p) = self.unsafe_vars.swap_remove(&RegType::Perm(i)) {
|
||||
if p == phase {
|
||||
*query_instr = Instruction::PutUnsafeValue(i, arg);
|
||||
self.safe_vars.insert(RegType::Perm(i));
|
||||
} else {
|
||||
self.unsafe_vars.insert(RegType::Perm(i), p);
|
||||
}
|
||||
}
|
||||
}
|
||||
&mut Instruction::SetValue(r) => {
|
||||
if !self.safe_vars.contains(&r) {
|
||||
*query_instr = Instruction::SetLocalValue(r);
|
||||
|
||||
self.safe_vars.insert(r);
|
||||
self.unsafe_vars.remove(&r);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+154
-43
@@ -1,13 +1,14 @@
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::disjuncts::VarData;
|
||||
use crate::machine::heap::*;
|
||||
use crate::machine::loader::PredicateQueue;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::parser::CompositeOpDesc;
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use crate::types::*;
|
||||
|
||||
use fxhash::FxBuildHasher;
|
||||
@@ -19,26 +20,23 @@ use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::ops::AddAssign;
|
||||
use std::ops::{AddAssign, Deref, DerefMut};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{is_infix, is_postfix};
|
||||
|
||||
pub type PredicateKey = (Atom, usize); // name, arity.
|
||||
|
||||
pub type Predicate = Vec<PredicateClause>;
|
||||
|
||||
/*
|
||||
// vars of predicate, toplevel offset. Vec<Term> is always a vector
|
||||
// of vars (we get their adjoining cells this way).
|
||||
pub type JumpStub = Vec<Term>;
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum TopLevel {
|
||||
Fact(Term), // Term, line_num, col_num
|
||||
Predicate(Predicate),
|
||||
Query(Vec<QueryTerm>),
|
||||
Rule(Rule), // Rule, line_num, col_num
|
||||
Fact(Fact, VarData), // Term, line_num, col_num
|
||||
Rule(Rule, VarData), // Rule, line_num, col_num
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -57,7 +55,13 @@ impl AppendOrPrepend {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum VarComparison {
|
||||
Indistinct,
|
||||
Distinct
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Level {
|
||||
Deep,
|
||||
Root,
|
||||
@@ -79,38 +83,144 @@ pub enum CallPolicy {
|
||||
Counted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ChunkType {
|
||||
Head,
|
||||
Mid,
|
||||
Last,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RootIterationPolicy {
|
||||
Iterated,
|
||||
NotIterated,
|
||||
}
|
||||
|
||||
impl RootIterationPolicy {
|
||||
#[inline(always)]
|
||||
pub fn iterable(&self) -> bool {
|
||||
if let RootIterationPolicy::Iterated = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkType {
|
||||
#[inline(always)]
|
||||
pub fn to_gen_context(self, chunk_num: usize) -> GenContext {
|
||||
match self {
|
||||
ChunkType::Head => GenContext::Head,
|
||||
ChunkType::Mid => GenContext::Mid(chunk_num),
|
||||
ChunkType::Last => GenContext::Last(chunk_num),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_last(self) -> bool {
|
||||
self == ChunkType::Last
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ChunkedTerms {
|
||||
Branch(Vec<VecDeque<ChunkedTerms>>),
|
||||
Chunk(VecDeque<QueryTerm>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChunkedTermVec {
|
||||
pub chunk_vec: VecDeque<ChunkedTerms>,
|
||||
}
|
||||
|
||||
impl Deref for ChunkedTermVec {
|
||||
type Target = VecDeque<ChunkedTerms>;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.chunk_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ChunkedTermVec {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.chunk_vec
|
||||
}
|
||||
}
|
||||
|
||||
impl ChunkedTermVec {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self { chunk_vec: VecDeque::new() }
|
||||
}
|
||||
|
||||
pub fn reserve_branch(&mut self, capacity: usize) {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Branch(Vec::with_capacity(capacity)));
|
||||
}
|
||||
|
||||
pub fn push_branch_arm(&mut self, branch: VecDeque<ChunkedTerms>) {
|
||||
match self.chunk_vec.back_mut().unwrap() {
|
||||
ChunkedTerms::Branch(branches) => {
|
||||
branches.push(branch);
|
||||
}
|
||||
ChunkedTerms::Chunk(_) => {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Branch(vec![branch]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_chunk(&mut self) {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![])));
|
||||
}
|
||||
|
||||
pub fn push_chunk_term(&mut self, term: QueryTerm) {
|
||||
match self.chunk_vec.back_mut() {
|
||||
Some(ChunkedTerms::Branch(_)) => {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
}
|
||||
Some(ChunkedTerms::Chunk(chunk)) => {
|
||||
chunk.push_back(term);
|
||||
}
|
||||
None => {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryTerm {
|
||||
// register, clause type, subterms, clause call policy.
|
||||
Clause(Cell<RegType>, ClauseType, Vec<Term>, CallPolicy),
|
||||
BlockedCut, // a cut which is 'blocked by letters', like the P term in P -> Q.
|
||||
UnblockedCut(Cell<VarReg>),
|
||||
GetLevelAndUnify(Cell<VarReg>, Rc<String>),
|
||||
Jump(JumpStub),
|
||||
Fail,
|
||||
LocalCut(usize), // var_num
|
||||
GlobalCut(usize), // var_num
|
||||
GetCutPoint { var_num: usize, prev_b: bool },
|
||||
GetLevel(usize), // var_num
|
||||
}
|
||||
|
||||
impl QueryTerm {
|
||||
pub(crate) fn set_call_policy(&mut self, cp: CallPolicy) {
|
||||
match self {
|
||||
&mut QueryTerm::Clause(_, _, _, ref mut clause_cp) => *clause_cp = cp,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn arity(&self) -> usize {
|
||||
match self {
|
||||
&QueryTerm::Clause(_, _, ref subterms, ..) => subterms.len(),
|
||||
&QueryTerm::BlockedCut | &QueryTerm::UnblockedCut(..) => 0,
|
||||
&QueryTerm::Jump(ref vars) => vars.len(),
|
||||
&QueryTerm::GetLevelAndUnify(..) => 1,
|
||||
&QueryTerm::GetLevel(_) | &QueryTerm::GetCutPoint { .. } => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Fact {
|
||||
pub(crate) head: Term,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Rule {
|
||||
pub(crate) head: (Atom, Vec<Term>, QueryTerm),
|
||||
pub(crate) clauses: Vec<QueryTerm>,
|
||||
pub(crate) head: (Atom, Vec<Term>),
|
||||
pub(crate) clauses: ChunkedTermVec,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash)]
|
||||
@@ -201,29 +311,29 @@ impl ClauseInfo for Rule {
|
||||
impl ClauseInfo for PredicateClause {
|
||||
fn name(&self) -> Option<Atom> {
|
||||
match self {
|
||||
&PredicateClause::Fact(ref term, ..) => term.name(),
|
||||
&PredicateClause::Fact(ref term, ..) => term.head.name(),
|
||||
&PredicateClause::Rule(ref rule, ..) => rule.name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
match self {
|
||||
&PredicateClause::Fact(ref term, ..) => term.arity(),
|
||||
&PredicateClause::Fact(ref term, ..) => term.head.arity(),
|
||||
&PredicateClause::Rule(ref rule, ..) => rule.arity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum PredicateClause {
|
||||
Fact(Term),
|
||||
Rule(Rule),
|
||||
Fact(Fact, VarData),
|
||||
Rule(Rule, VarData),
|
||||
}
|
||||
|
||||
impl PredicateClause {
|
||||
pub(crate) fn args(&self) -> Option<&[Term]> {
|
||||
match self {
|
||||
PredicateClause::Fact(term, ..) => match term {
|
||||
PredicateClause::Fact(term, ..) => match &term.head {
|
||||
Term::Clause(_, _, args) => Some(&args),
|
||||
_ => None,
|
||||
},
|
||||
@@ -661,9 +771,9 @@ impl Number {
|
||||
pub(crate) fn is_positive(&self) -> bool {
|
||||
match self {
|
||||
&Number::Fixnum(n) => n.get_num() > 0,
|
||||
&Number::Integer(ref n) => &**n > &0,
|
||||
&Number::Integer(ref n) => &**n > &Integer::from(0),
|
||||
&Number::Float(f) => f.is_sign_positive(),
|
||||
&Number::Rational(ref r) => &**r > &0,
|
||||
&Number::Rational(ref r) => &**r > &Rational::from(0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,9 +781,9 @@ impl Number {
|
||||
pub(crate) fn is_negative(&self) -> bool {
|
||||
match self {
|
||||
&Number::Fixnum(n) => n.get_num() < 0,
|
||||
&Number::Integer(ref n) => &**n < &0,
|
||||
&Number::Integer(ref n) => &**n < &Integer::from(0),
|
||||
&Number::Float(OrderedFloat(f)) => f.is_sign_negative() && OrderedFloat(f) != -0f64,
|
||||
&Number::Rational(ref r) => &**r < &0,
|
||||
&Number::Rational(ref r) => &**r < &Rational::from(0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,9 +791,9 @@ impl Number {
|
||||
pub(crate) fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
&Number::Fixnum(n) => n.get_num() == 0,
|
||||
&Number::Integer(ref n) => &**n == &0,
|
||||
&Number::Integer(ref n) => &**n == &Integer::from(0),
|
||||
&Number::Float(f) => f == OrderedFloat(0f64) || f == OrderedFloat(-0f64),
|
||||
&Number::Rational(ref r) => &**r == &0,
|
||||
&Number::Rational(ref r) => &**r == &Rational::from(0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -812,8 +922,9 @@ impl PredicateInfo {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn must_retract_local_clauses(&self) -> bool {
|
||||
self.is_extensible && self.has_clauses && !self.is_discontiguous
|
||||
pub(crate) fn must_retract_local_clauses(&self, is_cross_module_clause: bool) -> bool {
|
||||
self.is_extensible && self.has_clauses && !self.is_discontiguous &&
|
||||
!(self.is_multifile && is_cross_module_clause)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+315
-114
@@ -1,8 +1,9 @@
|
||||
#[cfg(test)]
|
||||
pub(crate) use crate::machine::gc::{IteratorUMP, StacklessPreOrderHeapIter};
|
||||
use crate::machine::heap::*;
|
||||
|
||||
use crate::atom_table::*;
|
||||
use crate::machine::heap::*;
|
||||
use crate::machine::stack::*;
|
||||
use crate::types::*;
|
||||
|
||||
use modular_bitfield::prelude::*;
|
||||
@@ -18,28 +19,45 @@ enum IterStackLocTag {
|
||||
PendingMark,
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[bits = 1]
|
||||
pub enum HeapOrStackTag {
|
||||
Heap,
|
||||
Stack,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[repr(u64)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct IterStackLoc {
|
||||
value: B62,
|
||||
pub value: B61,
|
||||
tag: IterStackLocTag,
|
||||
heap_or_stack: HeapOrStackTag,
|
||||
}
|
||||
|
||||
impl IterStackLoc {
|
||||
#[inline]
|
||||
pub fn iterable_heap_loc(h: usize) -> Self {
|
||||
IterStackLoc::new().with_tag(IterStackLocTag::Iterable).with_value(h as u64)
|
||||
pub fn iterable_loc(h: usize, heap_or_stack: HeapOrStackTag) -> Self {
|
||||
IterStackLoc::new()
|
||||
.with_tag(IterStackLocTag::Iterable)
|
||||
.with_heap_or_stack(heap_or_stack)
|
||||
.with_value(h as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mark_heap_loc(h: usize) -> Self {
|
||||
IterStackLoc::new().with_tag(IterStackLocTag::Marked).with_value(h as u64)
|
||||
fn mark_loc(h: usize, heap_or_stack: HeapOrStackTag) -> Self {
|
||||
IterStackLoc::new()
|
||||
.with_tag(IterStackLocTag::Marked)
|
||||
.with_heap_or_stack(heap_or_stack)
|
||||
.with_value(h as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pending_mark_heap_loc(h: usize) -> Self {
|
||||
IterStackLoc::new().with_tag(IterStackLocTag::PendingMark).with_value(h as u64)
|
||||
fn pending_mark_loc(h: usize, heap_or_stack: HeapOrStackTag) -> Self {
|
||||
IterStackLoc::new()
|
||||
.with_tag(IterStackLocTag::PendingMark)
|
||||
.with_heap_or_stack(heap_or_stack)
|
||||
.with_value(h as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -51,38 +69,35 @@ impl IterStackLoc {
|
||||
pub fn is_pending_mark(self) -> bool {
|
||||
self.tag() == IterStackLocTag::PendingMark
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn forward_if_referent_marked(heap: &mut [HeapCellValue], h: usize) {
|
||||
read_heap_cell!(heap[h],
|
||||
(HeapCellValueTag::Str
|
||||
| HeapCellValueTag::Lis
|
||||
| HeapCellValueTag::AttrVar
|
||||
| HeapCellValueTag::Var
|
||||
| HeapCellValueTag::PStrLoc, vh) => {
|
||||
if heap[vh].get_mark_bit() {
|
||||
heap[h].set_forwarding_bit(true);
|
||||
#[inline]
|
||||
pub fn as_ref(self) -> Ref {
|
||||
match self.heap_or_stack() {
|
||||
HeapOrStackTag::Heap => {
|
||||
Ref::heap_cell(self.value() as usize)
|
||||
}
|
||||
HeapOrStackTag::Stack => {
|
||||
Ref::stack_cell(self.value() as usize)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StackfulPreOrderHeapIter<'a> {
|
||||
pub heap: &'a mut Vec<HeapCellValue>,
|
||||
pub machine_stack: &'a mut Stack,
|
||||
stack: Vec<IterStackLoc>,
|
||||
h: usize,
|
||||
h: IterStackLoc,
|
||||
}
|
||||
|
||||
impl<'a> Drop for StackfulPreOrderHeapIter<'a> {
|
||||
fn drop(&mut self) {
|
||||
while let Some(h) = self.stack.pop() {
|
||||
let h = h.value() as usize;
|
||||
let cell = self.read_cell_mut(h);
|
||||
|
||||
self.heap[h].set_forwarding_bit(false);
|
||||
self.heap[h].set_mark_bit(false);
|
||||
cell.set_forwarding_bit(false);
|
||||
cell.set_mark_bit(false);
|
||||
}
|
||||
|
||||
self.heap.pop();
|
||||
@@ -90,48 +105,93 @@ impl<'a> Drop for StackfulPreOrderHeapIter<'a> {
|
||||
}
|
||||
|
||||
pub trait FocusedHeapIter: Iterator<Item = HeapCellValue> {
|
||||
fn focus(&self) -> usize;
|
||||
fn focus(&self) -> IterStackLoc;
|
||||
}
|
||||
|
||||
impl<'a> FocusedHeapIter for StackfulPreOrderHeapIter<'a> {
|
||||
#[inline]
|
||||
fn focus(&self) -> usize {
|
||||
fn focus(&self) -> IterStackLoc {
|
||||
self.h
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StackfulPreOrderHeapIter<'a> {
|
||||
#[inline]
|
||||
fn new(heap: &'a mut Vec<HeapCellValue>, cell: HeapCellValue) -> Self {
|
||||
let h = heap.len();
|
||||
fn new(heap: &'a mut Vec<HeapCellValue>, stack: &'a mut Stack, cell: HeapCellValue) -> Self {
|
||||
let h = IterStackLoc::iterable_loc(heap.len(), HeapOrStackTag::Heap);
|
||||
heap.push(cell);
|
||||
|
||||
Self {
|
||||
heap,
|
||||
h,
|
||||
stack: vec![IterStackLoc::iterable_heap_loc(h)],
|
||||
machine_stack: stack,
|
||||
stack: vec![h],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_stack(&mut self, h: usize) {
|
||||
self.stack.push(IterStackLoc::iterable_heap_loc(h));
|
||||
fn forward_if_referent_marked(&mut self, loc: IterStackLoc) {
|
||||
read_heap_cell!(self.read_cell(loc),
|
||||
(HeapCellValueTag::Str |
|
||||
HeapCellValueTag::Lis |
|
||||
HeapCellValueTag::AttrVar |
|
||||
HeapCellValueTag::Var |
|
||||
HeapCellValueTag::PStrLoc, vh) => {
|
||||
if self.heap[vh].get_mark_bit() {
|
||||
self.read_cell_mut(loc).set_forwarding_bit(true);
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::StackVar, vs) => {
|
||||
if self.machine_stack[vs].get_mark_bit() {
|
||||
self.read_cell_mut(loc).set_forwarding_bit(true);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stack_last(&self) -> Option<usize> {
|
||||
pub fn push_stack(&mut self, h: IterStackLoc) {
|
||||
self.stack.push(h);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_cell_mut(&mut self, loc: IterStackLoc) -> &mut HeapCellValue {
|
||||
match loc.heap_or_stack() {
|
||||
HeapOrStackTag::Heap => {
|
||||
&mut self.heap[loc.value() as usize]
|
||||
}
|
||||
HeapOrStackTag::Stack => {
|
||||
&mut self.machine_stack[loc.value() as usize]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_cell(&self, loc: IterStackLoc) -> HeapCellValue {
|
||||
match loc.heap_or_stack() {
|
||||
HeapOrStackTag::Heap => {
|
||||
self.heap[loc.value() as usize]
|
||||
}
|
||||
HeapOrStackTag::Stack => {
|
||||
self.machine_stack[loc.value() as usize]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stack_last(&self) -> Option<IterStackLoc> {
|
||||
for h in self.stack.iter().rev() {
|
||||
let is_readable_marked = h.is_marked();
|
||||
let h = h.value() as usize;
|
||||
let cell = self.heap[h];
|
||||
let cell = self.read_cell(*h);
|
||||
|
||||
if cell.get_forwarding_bit() {
|
||||
return Some(h);
|
||||
return Some(*h);
|
||||
} else if cell.get_mark_bit() && !is_readable_marked {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some(h);
|
||||
return Some(*h);
|
||||
}
|
||||
|
||||
None
|
||||
@@ -141,10 +201,9 @@ impl<'a> StackfulPreOrderHeapIter<'a> {
|
||||
pub fn pop_stack(&mut self) -> Option<HeapCellValue> {
|
||||
while let Some(h) = self.stack.pop() {
|
||||
let is_readable_marked = h.is_marked();
|
||||
let h = h.value() as usize;
|
||||
self.h = h;
|
||||
|
||||
let cell = &mut self.heap[h];
|
||||
self.h = h;
|
||||
let cell = self.read_cell_mut(h);
|
||||
|
||||
if cell.get_forwarding_bit() {
|
||||
cell.set_forwarding_bit(false);
|
||||
@@ -159,30 +218,34 @@ impl<'a> StackfulPreOrderHeapIter<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
fn push_if_unmarked(&mut self, h: usize) {
|
||||
if !self.heap[h].get_mark_bit() {
|
||||
self.heap[h].set_mark_bit(true);
|
||||
self.stack.push(IterStackLoc::iterable_heap_loc(h));
|
||||
#[inline]
|
||||
pub fn stack_len(&self) -> usize {
|
||||
self.stack.len()
|
||||
}
|
||||
|
||||
fn push_if_unmarked(&mut self, loc: IterStackLoc) {
|
||||
let cell = self.read_cell_mut(loc);
|
||||
|
||||
if !cell.get_mark_bit() {
|
||||
cell.set_mark_bit(true);
|
||||
self.stack.push(IterStackLoc::iterable_loc(loc.value() as usize, loc.heap_or_stack()));
|
||||
}
|
||||
}
|
||||
|
||||
fn follow(&mut self) -> Option<HeapCellValue> {
|
||||
while let Some(h) = self.stack.pop() {
|
||||
if h.is_pending_mark() {
|
||||
let h = h.value() as usize;
|
||||
|
||||
self.push_if_unmarked(h);
|
||||
self.stack.push(IterStackLoc::mark_heap_loc(h));
|
||||
self.stack.push(IterStackLoc::mark_loc(h.value() as usize, h.heap_or_stack()));
|
||||
|
||||
forward_if_referent_marked(&mut self.heap, h);
|
||||
self.forward_if_referent_marked(h);
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_readable_marked = h.is_marked();
|
||||
let h = h.value() as usize;
|
||||
|
||||
self.h = h;
|
||||
let cell = &mut self.heap[h];
|
||||
|
||||
let is_readable_marked = h.is_marked();
|
||||
let cell = self.read_cell_mut(h);
|
||||
|
||||
if cell.get_forwarding_bit() {
|
||||
let copy = *cell;
|
||||
@@ -195,50 +258,68 @@ impl<'a> StackfulPreOrderHeapIter<'a> {
|
||||
|
||||
read_heap_cell!(*cell,
|
||||
(HeapCellValueTag::Str | HeapCellValueTag::PStrLoc, vh) => {
|
||||
self.push_if_unmarked(vh);
|
||||
self.stack.push(IterStackLoc::mark_heap_loc(vh));
|
||||
let loc = IterStackLoc::iterable_loc(vh, HeapOrStackTag::Heap);
|
||||
|
||||
self.push_if_unmarked(loc);
|
||||
self.stack.push(IterStackLoc::mark_loc(vh, HeapOrStackTag::Heap));
|
||||
}
|
||||
(HeapCellValueTag::Lis, vh) => {
|
||||
self.push_if_unmarked(vh);
|
||||
let loc = IterStackLoc::iterable_loc(vh, HeapOrStackTag::Heap);
|
||||
|
||||
self.stack.push(IterStackLoc::pending_mark_heap_loc(vh + 1));
|
||||
self.stack.push(IterStackLoc::mark_heap_loc(vh));
|
||||
self.push_if_unmarked(loc);
|
||||
|
||||
forward_if_referent_marked(&mut self.heap, vh);
|
||||
self.stack.push(IterStackLoc::pending_mark_loc(vh + 1, HeapOrStackTag::Heap));
|
||||
self.stack.push(IterStackLoc::mark_loc(vh, HeapOrStackTag::Heap));
|
||||
|
||||
return Some(self.heap[h]);
|
||||
self.forward_if_referent_marked(loc);
|
||||
|
||||
return Some(self.read_cell(h));
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var, vh) => {
|
||||
self.push_if_unmarked(vh);
|
||||
self.stack.push(IterStackLoc::mark_heap_loc(vh));
|
||||
forward_if_referent_marked(&mut self.heap, vh);
|
||||
let loc = IterStackLoc::iterable_loc(vh, HeapOrStackTag::Heap);
|
||||
|
||||
self.push_if_unmarked(loc);
|
||||
self.stack.push(IterStackLoc::mark_loc(vh, HeapOrStackTag::Heap));
|
||||
self.forward_if_referent_marked(loc);
|
||||
}
|
||||
(HeapCellValueTag::StackVar, vs) => {
|
||||
let loc = IterStackLoc::iterable_loc(vs, HeapOrStackTag::Stack);
|
||||
|
||||
self.push_if_unmarked(loc);
|
||||
self.stack.push(IterStackLoc::mark_loc(vs, HeapOrStackTag::Stack));
|
||||
self.forward_if_referent_marked(loc);
|
||||
}
|
||||
(HeapCellValueTag::PStrOffset, offset) => {
|
||||
self.push_if_unmarked(offset);
|
||||
self.stack.push(IterStackLoc::iterable_heap_loc(h+1));
|
||||
self.push_if_unmarked(IterStackLoc::iterable_loc(offset, HeapOrStackTag::Heap));
|
||||
self.stack.push(IterStackLoc::iterable_loc((h.value()+1) as usize, HeapOrStackTag::Heap));
|
||||
|
||||
return Some(self.heap[h]);
|
||||
return Some(self.read_cell(h));
|
||||
}
|
||||
(HeapCellValueTag::PStr) => {
|
||||
self.push_if_unmarked(h);
|
||||
let tail_loc = IterStackLoc::iterable_loc((h.value()+1) as usize, HeapOrStackTag::Heap);
|
||||
|
||||
self.stack.push(IterStackLoc::iterable_heap_loc(h+1));
|
||||
forward_if_referent_marked(&mut self.heap, h+1);
|
||||
self.push_if_unmarked(IterStackLoc::iterable_loc(h.value() as usize, HeapOrStackTag::Heap));
|
||||
self.stack.push(tail_loc);
|
||||
self.forward_if_referent_marked(tail_loc);
|
||||
|
||||
return Some(self.heap[h]);
|
||||
return Some(self.read_cell(h));
|
||||
}
|
||||
(HeapCellValueTag::Atom, (_name, arity)) => {
|
||||
for h in (h + 2 .. h + arity + 1).rev() {
|
||||
self.stack.push(IterStackLoc::pending_mark_heap_loc(h));
|
||||
let l = h.value() as usize;
|
||||
|
||||
for l in (l + 2 .. l + arity + 1).rev() {
|
||||
self.stack.push(IterStackLoc::pending_mark_loc(l, HeapOrStackTag::Heap));
|
||||
}
|
||||
|
||||
if arity > 0 {
|
||||
self.push_if_unmarked(h+1);
|
||||
self.stack.push(IterStackLoc::mark_heap_loc(h+1));
|
||||
forward_if_referent_marked(&mut self.heap, h+1);
|
||||
let first_arg_loc = IterStackLoc::iterable_loc(l+1, HeapOrStackTag::Heap);
|
||||
|
||||
self.push_if_unmarked(first_arg_loc);
|
||||
self.stack.push(IterStackLoc::mark_loc(l+1, HeapOrStackTag::Heap));
|
||||
self.forward_if_referent_marked(first_arg_loc);
|
||||
}
|
||||
|
||||
return Some(self.heap[h]);
|
||||
return Some(self.read_cell(h));
|
||||
}
|
||||
_ => {
|
||||
return Some(*cell);
|
||||
@@ -269,19 +350,20 @@ pub(crate) fn stackless_preorder_iter(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn stackful_preorder_iter(
|
||||
heap: &mut Vec<HeapCellValue>,
|
||||
pub(crate) fn stackful_preorder_iter<'a>(
|
||||
heap: &'a mut Vec<HeapCellValue>,
|
||||
stack: &'a mut Stack,
|
||||
cell: HeapCellValue,
|
||||
) -> StackfulPreOrderHeapIter {
|
||||
StackfulPreOrderHeapIter::new(heap, cell)
|
||||
) -> StackfulPreOrderHeapIter<'a> {
|
||||
StackfulPreOrderHeapIter::new(heap, stack, cell)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PostOrderIterator<Iter: FocusedHeapIter> {
|
||||
focus: usize,
|
||||
focus: IterStackLoc,
|
||||
base_iter: Iter,
|
||||
base_iter_valid: bool,
|
||||
parent_stack: Vec<(usize, HeapCellValue, usize)>, // number of children, parent node, focus.
|
||||
parent_stack: Vec<(usize, HeapCellValue, IterStackLoc)>, // number of children, parent node, focus.
|
||||
}
|
||||
|
||||
impl<Iter: FocusedHeapIter> Deref for PostOrderIterator<Iter> {
|
||||
@@ -295,7 +377,7 @@ impl<Iter: FocusedHeapIter> Deref for PostOrderIterator<Iter> {
|
||||
impl<Iter: FocusedHeapIter> PostOrderIterator<Iter> {
|
||||
pub(crate) fn new(base_iter: Iter) -> Self {
|
||||
PostOrderIterator {
|
||||
focus: 0,
|
||||
focus: IterStackLoc::iterable_loc(0, HeapOrStackTag::Heap),
|
||||
base_iter,
|
||||
base_iter_valid: true,
|
||||
parent_stack: vec![],
|
||||
@@ -352,7 +434,7 @@ impl<Iter: FocusedHeapIter> Iterator for PostOrderIterator<Iter> {
|
||||
|
||||
impl<Iter: FocusedHeapIter> FocusedHeapIter for PostOrderIterator<Iter> {
|
||||
#[inline(always)]
|
||||
fn focus(&self) -> usize {
|
||||
fn focus(&self) -> IterStackLoc {
|
||||
self.focus
|
||||
}
|
||||
}
|
||||
@@ -368,7 +450,8 @@ impl<Iter: FocusedHeapIter> PostOrderIterator<Iter> {
|
||||
if let Some((_child_count, item, focus)) = self.parent_stack.last() {
|
||||
read_heap_cell!(item,
|
||||
(HeapCellValueTag::Atom, (_name, arity)) => {
|
||||
return focus + arity >= idx_loc && *focus < idx_loc;
|
||||
let focus = focus.value() as usize;
|
||||
return focus + arity >= idx_loc && focus < idx_loc;
|
||||
}
|
||||
_ => {}
|
||||
);
|
||||
@@ -401,9 +484,10 @@ impl<'a> LeftistPostOrderHeapIter<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn stackful_post_order_iter<'a>(
|
||||
heap: &'a mut Heap,
|
||||
stack: &'a mut Stack,
|
||||
cell: HeapCellValue,
|
||||
) -> LeftistPostOrderHeapIter<'a> {
|
||||
PostOrderIterator::new(StackfulPreOrderHeapIter::new(heap, cell))
|
||||
PostOrderIterator::new(StackfulPreOrderHeapIter::new(heap, stack, cell))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1382,7 +1466,11 @@ mod tests {
|
||||
.extend(functor!(f_atom, [atom(a_atom), atom(b_atom)]));
|
||||
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, str_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
str_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1413,7 +1501,11 @@ mod tests {
|
||||
));
|
||||
|
||||
for _ in 0..20 {
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, str_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
str_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1441,7 +1533,12 @@ mod tests {
|
||||
{
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
let mut var = heap_loc_as_cell!(0);
|
||||
|
||||
// self-referencing variables are copied with their forwarding
|
||||
@@ -1463,7 +1560,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(1));
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1483,7 +1584,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(empty_list_as_cell!());
|
||||
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1515,7 +1620,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
// the cycle will be iterated twice before being detected.
|
||||
assert_eq!(
|
||||
@@ -1543,7 +1652,11 @@ mod tests {
|
||||
}
|
||||
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
// cut the iteration short to check that all cells are
|
||||
// unmarked and unforwarded by the Drop instance of
|
||||
@@ -1577,7 +1690,11 @@ mod tests {
|
||||
let pstr_cell = wam.machine_st.heap[pstr_var_cell.get_value() as usize];
|
||||
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(unmark_cell_bits!(iter.next().unwrap()), pstr_cell);
|
||||
assert_eq!(
|
||||
@@ -1597,7 +1714,11 @@ mod tests {
|
||||
let pstr_second_cell = wam.machine_st.heap[pstr_second_var_cell.get_value() as usize];
|
||||
|
||||
{
|
||||
let mut iter = stackful_preorder_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_preorder_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(unmark_cell_bits!(iter.next().unwrap()), pstr_cell);
|
||||
assert_eq!(unmark_cell_bits!(iter.next().unwrap()), pstr_second_cell);
|
||||
@@ -1616,7 +1737,12 @@ mod tests {
|
||||
wam.machine_st.heap.push(fixnum_as_cell!(Fixnum::build_with(0i64)));
|
||||
|
||||
{
|
||||
let mut iter = stackful_preorder_iter(&mut wam.machine_st.heap, pstr_loc_as_cell!(0));
|
||||
let mut iter = stackful_preorder_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
pstr_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
let pstr_offset_cell = pstr_offset_as_cell!(0);
|
||||
|
||||
// pstr_offset_cell.set_forwarding_bit(true);
|
||||
@@ -1641,7 +1767,12 @@ mod tests {
|
||||
wam.machine_st.heap.push(fixnum_as_cell!(Fixnum::build_with(1i64)));
|
||||
|
||||
{
|
||||
let mut iter = stackful_preorder_iter(&mut wam.machine_st.heap, pstr_loc_as_cell!(0));
|
||||
let mut iter = stackful_preorder_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
pstr_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
let pstr_offset_cell = pstr_offset_as_cell!(0);
|
||||
|
||||
// pstr_offset_cell.set_forwarding_bit(true);
|
||||
@@ -1654,7 +1785,7 @@ mod tests {
|
||||
|
||||
let h = iter.focus();
|
||||
|
||||
assert_eq!(h, 5);
|
||||
assert_eq!(h.value(), 5);
|
||||
assert_eq!(unmark_cell_bits!(iter.heap[4]), pstr_offset_as_cell!(0));
|
||||
assert_eq!(unmark_cell_bits!(iter.heap[5]), fixnum_as_cell!(Fixnum::build_with(1i64)));
|
||||
|
||||
@@ -1674,7 +1805,11 @@ mod tests {
|
||||
wam.machine_st.heap.extend(functor);
|
||||
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1733,7 +1868,11 @@ mod tests {
|
||||
wam.machine_st.heap[4] = list_loc_as_cell!(1);
|
||||
|
||||
{
|
||||
let mut iter = stackful_preorder_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_preorder_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1800,6 +1939,7 @@ mod tests {
|
||||
{
|
||||
let mut iter = StackfulPreOrderHeapIter::new(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
@@ -1831,6 +1971,7 @@ mod tests {
|
||||
{
|
||||
let mut iter = stackful_preorder_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
@@ -1865,6 +2006,7 @@ mod tests {
|
||||
{
|
||||
let mut iter = stackful_preorder_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
@@ -1899,7 +2041,11 @@ mod tests {
|
||||
.extend(functor!(f_atom, [atom(a_atom), atom(b_atom)]));
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, str_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
str_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1930,7 +2076,11 @@ mod tests {
|
||||
));
|
||||
|
||||
for _ in 0..20 { // 0000 {
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, str_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
str_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -1960,7 +2110,12 @@ mod tests {
|
||||
{
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
let mut var = heap_loc_as_cell!(0);
|
||||
|
||||
// self-referencing variables are copied with their forwarding
|
||||
@@ -1982,7 +2137,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(1));
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2002,7 +2161,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(empty_list_as_cell!());
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2034,7 +2197,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
// the cycle will be iterated twice before being detected.
|
||||
assert_eq!(
|
||||
@@ -2064,6 +2231,7 @@ mod tests {
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
@@ -2099,7 +2267,11 @@ mod tests {
|
||||
let pstr_cell = wam.machine_st.heap[pstr_var_cell.get_value() as usize];
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, pstr_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
pstr_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2118,7 +2290,11 @@ mod tests {
|
||||
let pstr_second_cell = wam.machine_st.heap[pstr_second_var_cell.get_value() as usize];
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, pstr_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
pstr_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2137,7 +2313,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(fixnum_as_cell!(Fixnum::build_with(0i64)));
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, pstr_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
pstr_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(iter.next().unwrap(), fixnum_as_cell!(Fixnum::build_with(0i64)));
|
||||
assert_eq!(unmark_cell_bits!(iter.next().unwrap()), pstr_offset_as_cell!(0));
|
||||
@@ -2152,7 +2332,11 @@ mod tests {
|
||||
wam.machine_st.heap.push(fixnum_as_cell!(Fixnum::build_with(1i64)));
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, pstr_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
pstr_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(iter.next().unwrap(), fixnum_as_cell!(Fixnum::build_with(1i64)));
|
||||
assert_eq!(unmark_cell_bits!(iter.next().unwrap()), pstr_offset_as_cell!(0));
|
||||
@@ -2176,7 +2360,11 @@ mod tests {
|
||||
wam.machine_st.heap.extend(functor);
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2236,7 +2424,11 @@ mod tests {
|
||||
wam.machine_st.heap[4] = list_loc_as_cell!(1);
|
||||
|
||||
{
|
||||
let mut iter = stackful_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackful_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
&mut wam.machine_st.stack,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2343,7 +2535,10 @@ mod tests {
|
||||
));
|
||||
|
||||
for _ in 0..20 {
|
||||
let mut iter = stackless_post_order_iter(&mut wam.machine_st.heap, str_loc_as_cell!(0));
|
||||
let mut iter = stackless_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
str_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(unmark_cell_bits!(iter.next().unwrap()), str_loc_as_cell!(0));
|
||||
|
||||
@@ -2373,7 +2568,10 @@ mod tests {
|
||||
{
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
let mut iter = stackless_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackless_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
@@ -2389,7 +2587,10 @@ mod tests {
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(1));
|
||||
wam.machine_st.heap.push(heap_loc_as_cell!(0));
|
||||
|
||||
let mut iter = stackless_post_order_iter(&mut wam.machine_st.heap, heap_loc_as_cell!(0));
|
||||
let mut iter = stackless_post_order_iter(
|
||||
&mut wam.machine_st.heap,
|
||||
heap_loc_as_cell!(0),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unmark_cell_bits!(iter.next().unwrap()),
|
||||
|
||||
+404
-261
File diff suppressed because it is too large
Load Diff
+43
-14
@@ -1,25 +1,54 @@
|
||||
use std::sync::Arc;
|
||||
use std::convert::Infallible;
|
||||
|
||||
use hyper::{Response, Request, Body};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex, Condvar};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use http_body_util::Full;
|
||||
use bytes::Bytes;
|
||||
use hyper::service::Service;
|
||||
use hyper::{body::Incoming as IncomingBody, Request, Response};
|
||||
|
||||
pub struct HttpListener {
|
||||
pub incoming: Receiver<HttpRequest>
|
||||
pub incoming: std::sync::mpsc::Receiver<HttpRequest>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HttpRequest {
|
||||
pub request: Request<Body>,
|
||||
pub request: Request<IncomingBody>,
|
||||
pub response: HttpResponse,
|
||||
}
|
||||
|
||||
pub type HttpResponse = Sender<Response<Body>>;
|
||||
pub type HttpResponse = Arc<(Mutex<bool>, Mutex<Option<Response<Full<Bytes>>>>, Condvar)>;
|
||||
|
||||
pub async fn serve_req(req: Request<Body>, tx: Arc<Mutex<Sender<HttpRequest>>>) -> Result<Response<Body>, Infallible> {
|
||||
let (response_tx, mut rx) = channel(1);
|
||||
let http_request = HttpRequest { request: req, response: response_tx };
|
||||
tx.lock().await.send(http_request).await.unwrap();
|
||||
Ok(rx.recv().await.unwrap())
|
||||
pub struct HttpService {
|
||||
pub tx: std::sync::mpsc::SyncSender<HttpRequest>,
|
||||
}
|
||||
|
||||
impl Service<Request<IncomingBody>> for HttpService {
|
||||
type Response = Response<Full<Bytes>>;
|
||||
type Error = hyper::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn call(&mut self, req: Request<IncomingBody>) -> Self::Future {
|
||||
// new connection!
|
||||
// we send the Request info to Prolog
|
||||
let response = Arc::new((Mutex::new(false), Mutex::new(None), Condvar::new()));
|
||||
let http_request = HttpRequest { request: req, response: Arc::clone(&response) };
|
||||
self.tx.send(http_request).unwrap();
|
||||
|
||||
// we wait for the Response info from Prolog
|
||||
{
|
||||
let (ready, _response, cvar) = &*response;
|
||||
let mut ready = ready.lock().unwrap();
|
||||
while !*ready {
|
||||
ready = cvar.wait(ready).unwrap();
|
||||
}
|
||||
}
|
||||
{
|
||||
let (_, response, _) = &*response;
|
||||
let response = response.lock().unwrap().take();
|
||||
let res = response.expect("Data race error in HTTP Server");
|
||||
Box::pin(async move {
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+138
-222
@@ -5,9 +5,7 @@ use crate::parser::ast::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::iter::*;
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -18,34 +16,36 @@ pub(crate) enum TermRef<'a> {
|
||||
Clause(Level, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||
PartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Atom),
|
||||
Var(Level, &'a Cell<VarReg>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
/*
|
||||
impl<'a> TermRef<'a> {
|
||||
pub(crate) fn level(self) -> Level {
|
||||
pub(crate) fn level(&self) -> Level {
|
||||
match self {
|
||||
TermRef::AnonVar(lvl)
|
||||
| TermRef::Cons(lvl, ..)
|
||||
| TermRef::Literal(lvl, ..)
|
||||
| TermRef::Var(lvl, ..)
|
||||
| TermRef::Clause(lvl, ..)
|
||||
| TermRef::CompleteString(lvl, ..)
|
||||
| TermRef::PartialString(lvl, ..) => lvl,
|
||||
TermRef::AnonVar(lvl) |
|
||||
TermRef::Cons(lvl, ..) |
|
||||
TermRef::Literal(lvl, ..) |
|
||||
TermRef::Var(lvl, ..) |
|
||||
TermRef::Clause(lvl, ..) |
|
||||
TermRef::CompleteString(lvl, ..) |
|
||||
TermRef::PartialString(lvl, ..) => *lvl,
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TermIterState<'a> {
|
||||
AnonVar(Level),
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
Clause(Level, usize, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
InitialPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||
FinalPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Atom),
|
||||
Var(Level, &'a Cell<VarReg>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
impl<'a> TermIterState<'a> {
|
||||
@@ -65,7 +65,7 @@ impl<'a> TermIterState<'a> {
|
||||
Term::CompleteString(cell, atom) => {
|
||||
TermIterState::CompleteString(lvl, cell, *atom)
|
||||
}
|
||||
Term::Var(cell, var) => TermIterState::Var(lvl, cell, var.clone()),
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,10 +77,10 @@ pub(crate) struct QueryIterator<'a> {
|
||||
|
||||
impl<'a> QueryIterator<'a> {
|
||||
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
|
||||
self.state_stack
|
||||
.push(TermIterState::subterm_to_state(lvl, term));
|
||||
self.state_stack.push(TermIterState::subterm_to_state(lvl, term));
|
||||
}
|
||||
|
||||
/*
|
||||
fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
|
||||
let state_stack = terms
|
||||
.iter()
|
||||
@@ -90,6 +90,7 @@ impl<'a> QueryIterator<'a> {
|
||||
|
||||
QueryIterator { state_stack }
|
||||
}
|
||||
*/
|
||||
|
||||
fn from_term(term: &'a Term) -> Self {
|
||||
let state = match term {
|
||||
@@ -106,7 +107,7 @@ impl<'a> QueryIterator<'a> {
|
||||
*name,
|
||||
terms,
|
||||
),
|
||||
Term::Var(cell, var) => TermIterState::Var(Level::Root, cell, var.clone()),
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Root, cell, var_ptr.clone()),
|
||||
};
|
||||
|
||||
QueryIterator {
|
||||
@@ -114,46 +115,24 @@ impl<'a> QueryIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn new(term: &'a QueryTerm) -> Self {
|
||||
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
|
||||
match term {
|
||||
&QueryTerm::Clause(ref cell, ClauseType::CallN(_), ref terms, _) => {
|
||||
let state = TermIterState::Clause(Level::Root, 1, cell, atom!("$call"), terms);
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
self.state_stack.push(TermIterState::Clause(lvl, 1, cell, atom!("$call"), terms));
|
||||
}
|
||||
&QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
|
||||
let state = TermIterState::Clause(Level::Root, 0, cell, ct.name(), terms);
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
self.state_stack.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
|
||||
}
|
||||
&QueryTerm::UnblockedCut(ref cell) => {
|
||||
let state = TermIterState::Var(Level::Root, cell, Rc::new("!".to_string()));
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
&QueryTerm::GetLevelAndUnify(ref cell, ref var) => {
|
||||
let state = TermIterState::Var(Level::Root, cell, var.clone());
|
||||
QueryIterator {
|
||||
state_stack: vec![state],
|
||||
}
|
||||
}
|
||||
&QueryTerm::Jump(ref vars) => {
|
||||
let state_stack = vars
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|t| TermIterState::subterm_to_state(Level::Shallow, t))
|
||||
.collect();
|
||||
|
||||
QueryIterator { state_stack }
|
||||
}
|
||||
&QueryTerm::BlockedCut => QueryIterator {
|
||||
state_stack: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(term: &'a QueryTerm) -> Self {
|
||||
let mut iter = QueryIterator { state_stack: vec![] };
|
||||
iter.extend_state(Level::Root, term);
|
||||
iter
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for QueryIterator<'a> {
|
||||
@@ -212,8 +191,8 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
TermIterState::Literal(lvl, cell, constant) => {
|
||||
return Some(TermRef::Literal(lvl, cell, constant));
|
||||
}
|
||||
TermIterState::Var(lvl, cell, var) => {
|
||||
return Some(TermRef::Var(lvl, cell, var));
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(TermRef::Var(lvl, cell, var_ptr));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -225,7 +204,7 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FactIterator<'a> {
|
||||
state_queue: VecDeque<TermIterState<'a>>,
|
||||
iterable_root: bool,
|
||||
iterable_root: RootIterationPolicy,
|
||||
}
|
||||
|
||||
impl<'a> FactIterator<'a> {
|
||||
@@ -242,11 +221,11 @@ impl<'a> FactIterator<'a> {
|
||||
|
||||
FactIterator {
|
||||
state_queue,
|
||||
iterable_root: false,
|
||||
iterable_root: RootIterationPolicy::NotIterated,
|
||||
}
|
||||
}
|
||||
|
||||
fn new(term: &'a Term, iterable_root: bool) -> Self {
|
||||
fn new(term: &'a Term, iterable_root: RootIterationPolicy) -> Self {
|
||||
let states = match term {
|
||||
Term::AnonVar => {
|
||||
vec![TermIterState::AnonVar(Level::Root)]
|
||||
@@ -278,8 +257,8 @@ impl<'a> FactIterator<'a> {
|
||||
Term::Literal(cell, constant) => {
|
||||
vec![TermIterState::Literal(Level::Root, cell, constant)]
|
||||
}
|
||||
Term::Var(cell, var) => {
|
||||
vec![TermIterState::Var(Level::Root, cell, var.clone())]
|
||||
Term::Var(cell, var_ptr) => {
|
||||
vec![TermIterState::Var(Level::Root, cell, var_ptr.clone())]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -305,7 +284,7 @@ impl<'a> Iterator for FactIterator<'a> {
|
||||
}
|
||||
|
||||
match lvl {
|
||||
Level::Root if !self.iterable_root => continue,
|
||||
Level::Root if !self.iterable_root.iterable() => continue,
|
||||
_ => return Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||
};
|
||||
}
|
||||
@@ -325,8 +304,8 @@ impl<'a> Iterator for FactIterator<'a> {
|
||||
TermIterState::Literal(lvl, cell, constant) => {
|
||||
return Some(TermRef::Literal(lvl, cell, constant))
|
||||
}
|
||||
TermIterState::Var(lvl, cell, var) => {
|
||||
return Some(TermRef::Var(lvl, cell, var));
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(TermRef::Var(lvl, cell, var_ptr));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -340,193 +319,130 @@ pub(crate) fn post_order_iter<'a>(term: &'a Term) -> QueryIterator<'a> {
|
||||
QueryIterator::from_term(term)
|
||||
}
|
||||
|
||||
pub(crate) fn breadth_first_iter<'a>(term: &'a Term, iterable_root: bool) -> FactIterator<'a> {
|
||||
pub(crate) fn breadth_first_iter<'a>(term: &'a Term, iterable_root: RootIterationPolicy) -> FactIterator<'a> {
|
||||
FactIterator::new(term, iterable_root)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ClauseIteratorState<'a> {
|
||||
RemainingChunks(&'a VecDeque<ChunkedTerms>, usize),
|
||||
RemainingBranches(&'a Vec<VecDeque<ChunkedTerms>>, usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ClauseItem<'a> {
|
||||
FirstBranch(usize),
|
||||
NextBranch,
|
||||
BranchEnd(usize),
|
||||
Chunk(&'a VecDeque<QueryTerm>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ChunkedTerm<'a> {
|
||||
HeadClause(Atom, &'a Vec<Term>),
|
||||
BodyTerm(&'a QueryTerm),
|
||||
pub(crate) struct ClauseIterator<'a> {
|
||||
state_stack: Vec<ClauseIteratorState<'a>>,
|
||||
remaining_chunks_on_stack: usize,
|
||||
}
|
||||
|
||||
pub(crate) fn query_term_post_order_iter<'a>(query_term: &'a QueryTerm) -> QueryIterator<'a> {
|
||||
QueryIterator::new(query_term)
|
||||
}
|
||||
|
||||
impl<'a> ChunkedTerm<'a> {
|
||||
pub(crate) fn post_order_iter(&self) -> QueryIterator<'a> {
|
||||
match self {
|
||||
&ChunkedTerm::BodyTerm(qt) => QueryIterator::new(qt),
|
||||
&ChunkedTerm::HeadClause(_, terms) => QueryIterator::from_rule_head_clause(terms),
|
||||
fn state_from_chunked_terms<'a>(chunk_vec: &'a VecDeque<ChunkedTerms>) -> ClauseIteratorState<'a> {
|
||||
if chunk_vec.len() == 1 {
|
||||
if let Some(ChunkedTerms::Branch(ref branches)) = chunk_vec.front() {
|
||||
return ClauseIteratorState::RemainingBranches(branches, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ClauseIteratorState::RemainingChunks(chunk_vec, 0)
|
||||
}
|
||||
|
||||
fn contains_cut_var<'a, Iter: Iterator<Item = &'a Term>>(terms: Iter) -> bool {
|
||||
for term in terms {
|
||||
if let &Term::Var(_, ref var) = term {
|
||||
if var.as_str() == "!" {
|
||||
return true;
|
||||
impl<'a> ClauseIterator<'a> {
|
||||
pub fn new(clauses: &'a ChunkedTermVec) -> Self {
|
||||
match state_from_chunked_terms(&clauses.chunk_vec) {
|
||||
state @ ClauseIteratorState::RemainingBranches(..) => {
|
||||
Self {
|
||||
state_stack: vec![state],
|
||||
remaining_chunks_on_stack: 0,
|
||||
}
|
||||
}
|
||||
state @ ClauseIteratorState::RemainingChunks(..) => {
|
||||
Self {
|
||||
state_stack: vec![state],
|
||||
remaining_chunks_on_stack: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) struct ChunkedIterator<'a> {
|
||||
pub(crate) chunk_num: usize,
|
||||
iter: Box<dyn Iterator<Item = ChunkedTerm<'a>> + 'a>,
|
||||
deep_cut_encountered: bool,
|
||||
cut_var_in_head: bool,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for ChunkedIterator<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("ChunkedIterator")
|
||||
.field("chunk_num", &self.chunk_num)
|
||||
// Hacky solution.
|
||||
.field("iter", &"Box<dyn Iterator<Item = ChunkedTerm<'a>> + 'a>")
|
||||
.field("deep_cut_encountered", &self.deep_cut_encountered)
|
||||
.field("cut_var_in_head", &self.cut_var_in_head)
|
||||
.finish()
|
||||
#[inline(always)]
|
||||
pub fn in_tail_position(&self) -> bool {
|
||||
self.remaining_chunks_on_stack == 0
|
||||
}
|
||||
}
|
||||
|
||||
type ChunkedIteratorItem<'a> = (usize, usize, Vec<ChunkedTerm<'a>>);
|
||||
type RuleBodyIteratorItem<'a> = (usize, usize, Vec<&'a QueryTerm>);
|
||||
fn branch_end_depth(&mut self) -> usize {
|
||||
let mut depth = 1;
|
||||
|
||||
impl<'a> ChunkedIterator<'a> {
|
||||
pub(crate) fn rule_body_iter(self) -> Box<dyn Iterator<Item = RuleBodyIteratorItem<'a>> + 'a> {
|
||||
Box::new(self.filter_map(|(cn, lt_arity, terms)| {
|
||||
let filtered_terms: Vec<_> = terms
|
||||
.into_iter()
|
||||
.filter_map(|ct| match ct {
|
||||
ChunkedTerm::BodyTerm(qt) => Some(qt),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if filtered_terms.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((cn, lt_arity, filtered_terms))
|
||||
while let Some(state) = self.state_stack.pop() {
|
||||
match state {
|
||||
ClauseIteratorState::RemainingBranches(terms, focus) if terms.len() == focus => {
|
||||
depth += 1;
|
||||
}
|
||||
_ => {
|
||||
self.state_stack.push(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn from_rule_body(p1: &'a QueryTerm, clauses: &'a Vec<QueryTerm>) -> Self {
|
||||
let inner_iter = Box::new(once(ChunkedTerm::BodyTerm(p1)));
|
||||
let iter = inner_iter.chain(clauses.iter().map(|t| ChunkedTerm::BodyTerm(t)));
|
||||
|
||||
ChunkedIterator {
|
||||
chunk_num: 0,
|
||||
iter: Box::new(iter),
|
||||
deep_cut_encountered: false,
|
||||
cut_var_in_head: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_rule(rule: &'a Rule) -> Self {
|
||||
let &Rule {
|
||||
head: (ref name, ref args, ref p1),
|
||||
ref clauses,
|
||||
} = rule;
|
||||
|
||||
let iter = once(ChunkedTerm::HeadClause(name.clone(), args));
|
||||
let inner_iter = Box::new(once(ChunkedTerm::BodyTerm(p1)));
|
||||
let iter = iter.chain(inner_iter.chain(clauses.iter().map(|t| ChunkedTerm::BodyTerm(t))));
|
||||
|
||||
ChunkedIterator {
|
||||
chunk_num: 0,
|
||||
iter: Box::new(iter),
|
||||
deep_cut_encountered: false,
|
||||
cut_var_in_head: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encountered_deep_cut(&self) -> bool {
|
||||
self.deep_cut_encountered
|
||||
}
|
||||
|
||||
fn take_chunk(&mut self, term: ChunkedTerm<'a>) -> (usize, usize, Vec<ChunkedTerm<'a>>) {
|
||||
let mut arity = 0;
|
||||
let mut item = Some(term);
|
||||
let mut result = Vec::new();
|
||||
|
||||
while let Some(term) = item {
|
||||
match term {
|
||||
ChunkedTerm::HeadClause(_, terms) => {
|
||||
if contains_cut_var(terms.iter()) {
|
||||
self.cut_var_in_head = true;
|
||||
}
|
||||
|
||||
result.push(term);
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::Jump(ref vars)) => {
|
||||
result.push(term);
|
||||
arity = vars.len();
|
||||
|
||||
if contains_cut_var(vars.iter()) && !self.cut_var_in_head {
|
||||
self.deep_cut_encountered = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::BlockedCut) => {
|
||||
result.push(term);
|
||||
|
||||
if self.chunk_num > 0 {
|
||||
self.deep_cut_encountered = true;
|
||||
}
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::GetLevelAndUnify(..)) => {
|
||||
self.deep_cut_encountered = true;
|
||||
|
||||
result.push(term);
|
||||
arity = 1;
|
||||
break;
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::UnblockedCut(..)) => {
|
||||
self.deep_cut_encountered = true;
|
||||
result.push(term);
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::Clause(_, ClauseType::Inlined(_), ..)) => {
|
||||
result.push(term)
|
||||
}
|
||||
ChunkedTerm::BodyTerm(&QueryTerm::Clause(
|
||||
_,
|
||||
ClauseType::CallN(_),
|
||||
ref subterms,
|
||||
_,
|
||||
)) => {
|
||||
result.push(term);
|
||||
arity = subterms.len() + 1;
|
||||
break;
|
||||
}
|
||||
ChunkedTerm::BodyTerm(qt) => {
|
||||
result.push(term);
|
||||
arity = qt.arity();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
item = self.iter.next();
|
||||
}
|
||||
|
||||
let chunk_num = self.chunk_num;
|
||||
self.chunk_num += 1;
|
||||
|
||||
(chunk_num, arity, result)
|
||||
depth
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ChunkedIterator<'a> {
|
||||
// the chunk number, last term arity, and vector of references.
|
||||
type Item = ChunkedIteratorItem<'a>;
|
||||
impl<'a> Iterator for ClauseIterator<'a> {
|
||||
type Item = ClauseItem<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|term| self.take_chunk(term))
|
||||
while let Some(state) = self.state_stack.pop() {
|
||||
match state {
|
||||
ClauseIteratorState::RemainingChunks(chunks, focus) if focus < chunks.len() => {
|
||||
if focus + 1 < chunks.len() {
|
||||
self.state_stack.push(ClauseIteratorState::RemainingChunks(chunks, focus + 1));
|
||||
} else {
|
||||
self.remaining_chunks_on_stack -= 1;
|
||||
}
|
||||
|
||||
match &chunks[focus] {
|
||||
ChunkedTerms::Branch(branches) => {
|
||||
self.state_stack.push(ClauseIteratorState::RemainingBranches(branches, 0));
|
||||
}
|
||||
ChunkedTerms::Chunk(chunk) => {
|
||||
return Some(ClauseItem::Chunk(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
ClauseIteratorState::RemainingChunks(chunks, focus) => {
|
||||
debug_assert_eq!(chunks.len(), focus);
|
||||
}
|
||||
ClauseIteratorState::RemainingBranches(branches, focus) if focus < branches.len() => {
|
||||
self.state_stack.push(ClauseIteratorState::RemainingBranches(&branches, focus + 1));
|
||||
let state = state_from_chunked_terms(&branches[focus]);
|
||||
|
||||
if let ClauseIteratorState::RemainingChunks(..) = &state {
|
||||
self.remaining_chunks_on_stack += 1;
|
||||
}
|
||||
|
||||
self.state_stack.push(state);
|
||||
|
||||
return if focus == 0 {
|
||||
Some(ClauseItem::FirstBranch(branches.len()))
|
||||
} else {
|
||||
Some(ClauseItem::NextBranch)
|
||||
};
|
||||
}
|
||||
ClauseIteratorState::RemainingBranches(branches, focus) => {
|
||||
debug_assert_eq!(branches.len(), focus);
|
||||
return Some(ClauseItem::BranchEnd(self.branch_end_depth()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ mod allocator;
|
||||
mod arithmetic;
|
||||
pub mod codegen;
|
||||
mod debray_allocator;
|
||||
mod fixtures;
|
||||
mod ffi;
|
||||
mod variable_records;
|
||||
mod forms;
|
||||
mod heap_iter;
|
||||
pub mod heap_print;
|
||||
|
||||
+43
-1
@@ -1,4 +1,9 @@
|
||||
:- module(arithmetic, [expmod/4, lsb/2, msb/2, number_to_rational/2,
|
||||
/** Arithmetic predicates
|
||||
|
||||
These predicates are additions to standard the arithmetic functions provided by `is/2`.
|
||||
*/
|
||||
|
||||
:- module(arithmetic, [expmod/4, lcm/3, lsb/2, msb/2, number_to_rational/2,
|
||||
number_to_rational/3, popcount/2,
|
||||
rational_numerator_denominator/3]).
|
||||
|
||||
@@ -6,6 +11,10 @@
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(lists), [append/3, member/2]).
|
||||
|
||||
|
||||
%% expmod(+Base, +Expo, +Mod, -R).
|
||||
%
|
||||
% Modular exponentiation. Base, Expo and Mod must be integers.
|
||||
expmod(Base, Expo, Mod, R) :-
|
||||
( member(N, [Base, Expo, Mod]), var(N) -> instantiation_error(expmod/4)
|
||||
; member(N, [Base, Expo, Mod]), \+ integer(N) ->
|
||||
@@ -28,6 +37,25 @@ expmod_(Base0, Expo0, Mod, C, R) :-
|
||||
Base is (Base0 * Base0) mod Mod,
|
||||
expmod_(Base, Expo, Mod, C, R).
|
||||
|
||||
%% lcm(+A, +B, -Lcm) is det.
|
||||
%
|
||||
% Calculates the Least common multiple for A and B: the smallest positive integer
|
||||
% that is divisible by both A and B.
|
||||
%
|
||||
% A and B need to be integers.
|
||||
lcm(A, B, X) :-
|
||||
builtins:must_be_number(A, lcm/2),
|
||||
builtins:must_be_number(B, lcm/2),
|
||||
( \+ integer(A) -> type_error(integer, A, lcm/2)
|
||||
; \+ integer(B) -> type_error(integer, B, lcm/2)
|
||||
; (A = 0, B = 0) -> X = 0
|
||||
; builtins:can_be_number(X, lcm/2),
|
||||
X is abs(B) // gcd(A,B) * abs(A)
|
||||
).
|
||||
|
||||
%% lsb(+X, -N).
|
||||
%
|
||||
% True iff N is the least significat bit of integer X
|
||||
lsb(X, N) :-
|
||||
builtins:must_be_number(X, lsb/2),
|
||||
( \+ integer(X) -> type_error(integer, X, lsb/2)
|
||||
@@ -37,6 +65,9 @@ lsb(X, N) :-
|
||||
msb_(X1, -1, N)
|
||||
).
|
||||
|
||||
%% msb(+X, -N).
|
||||
%
|
||||
% True iff N is the most significant bit of integer X
|
||||
msb(X, N) :-
|
||||
builtins:must_be_number(X, msb/2),
|
||||
( \+ integer(X) -> type_error(integer, X, msb/2)
|
||||
@@ -52,6 +83,9 @@ msb_(X, M, N) :-
|
||||
M1 is M + 1,
|
||||
msb_(X1, M1, N).
|
||||
|
||||
%% number_to_rational(+Real, -Fraction).
|
||||
%
|
||||
% True iff given a number Real, Fraction is the same number represented as a fraction.
|
||||
number_to_rational(Real, Fraction) :-
|
||||
( var(Real) -> instantiation_error(number_to_rational/2)
|
||||
; integer(Real) -> Fraction is Real rdiv 1
|
||||
@@ -110,12 +144,20 @@ simplify_fraction(A0/B0, A/B) :-
|
||||
A is A0 // G,
|
||||
B is B0 // G.
|
||||
|
||||
%% rational_numerator_denominator(+Fraction, -Numerator, -Denominator).
|
||||
%
|
||||
% True iff given a fraction Fraction, Numerator is the numerator of that fraction
|
||||
% and Denominator the denominator.
|
||||
rational_numerator_denominator(R, N, D) :-
|
||||
write_term_to_chars(R, [], Cs),
|
||||
append(Ns, [' ', r, d, i, v, ' '|Ds], Cs),
|
||||
number_chars(N, Ns),
|
||||
number_chars(D, Ds).
|
||||
|
||||
%% popcount(+Number, -Bits1).
|
||||
%
|
||||
% True iff given an integer Number, Bits1 is the amount of 1 bits the binary representation
|
||||
% of that number has.
|
||||
popcount(X, N) :-
|
||||
must_be(integer, X),
|
||||
'$popcount'(X, N).
|
||||
|
||||
+61
-64
@@ -54,28 +54,27 @@
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
/** <module> Binary associations
|
||||
/** Binary associations
|
||||
|
||||
Assocs are Key-Value associations implemented as a balanced binary tree
|
||||
(AVL tree).
|
||||
|
||||
@see library(pairs), library(rbtrees)
|
||||
@author R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
||||
Authors: R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
||||
*/
|
||||
|
||||
:- meta_predicate map_assoc(1, ?).
|
||||
:- meta_predicate map_assoc(2, ?, ?).
|
||||
|
||||
%! empty_assoc(?Assoc) is semidet.
|
||||
%% empty_assoc(?Assoc) is semidet.
|
||||
%
|
||||
% Is true if Assoc is the empty association list.
|
||||
% Is true if Assoc is the empty association list.
|
||||
|
||||
empty_assoc(t).
|
||||
|
||||
%! assoc_to_list(+Assoc, -Pairs) is det.
|
||||
%% assoc_to_list(+Assoc, -Pairs) is det.
|
||||
%
|
||||
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
|
||||
% in Pairs are sorted in ascending order.
|
||||
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
|
||||
% in Pairs are sorted in ascending order.
|
||||
|
||||
assoc_to_list(Assoc, List) :-
|
||||
assoc_to_list(Assoc, List, []).
|
||||
@@ -86,10 +85,10 @@ assoc_to_list(t(Key,Val,_,L,R), List, Rest) :-
|
||||
assoc_to_list(t, List, List).
|
||||
|
||||
|
||||
%! assoc_to_keys(+Assoc, -Keys) is det.
|
||||
%% assoc_to_keys(+Assoc, -Keys) is det.
|
||||
%
|
||||
% True if Keys is the list of keys in Assoc. The keys are sorted
|
||||
% in ascending order.
|
||||
% True if Keys is the list of keys in Assoc. The keys are sorted
|
||||
% in ascending order.
|
||||
|
||||
assoc_to_keys(Assoc, List) :-
|
||||
assoc_to_keys(Assoc, List, []).
|
||||
@@ -100,11 +99,11 @@ assoc_to_keys(t(Key,_,_,L,R), List, Rest) :-
|
||||
assoc_to_keys(t, List, List).
|
||||
|
||||
|
||||
%! assoc_to_values(+Assoc, -Values) is det.
|
||||
%% assoc_to_values(+Assoc, -Values) is det.
|
||||
%
|
||||
% True if Values is the list of values in Assoc. Values are
|
||||
% ordered in ascending order of the key to which they were
|
||||
% associated. Values may contain duplicates.
|
||||
% True if Values is the list of values in Assoc. Values are
|
||||
% ordered in ascending order of the key to which they were
|
||||
% associated. Values may contain duplicates.
|
||||
|
||||
assoc_to_values(Assoc, List) :-
|
||||
assoc_to_values(Assoc, List, []).
|
||||
@@ -114,12 +113,12 @@ assoc_to_values(t(_,Value,_,L,R), List, Rest) :-
|
||||
assoc_to_values(R, More, Rest).
|
||||
assoc_to_values(t, List, List).
|
||||
|
||||
%! is_assoc(+Assoc) is semidet.
|
||||
%% is_assoc(+Assoc) is semidet.
|
||||
%
|
||||
% True if Assoc is an association list. This predicate checks
|
||||
% that the structure is valid, elements are in order, and tree
|
||||
% is balanced to the extent guaranteed by AVL trees. I.e.,
|
||||
% branches of each subtree differ in depth by at most 1.
|
||||
% True if Assoc is an association list. This predicate checks
|
||||
% that the structure is valid, elements are in order, and tree
|
||||
% is balanced to the extent guaranteed by AVL trees. I.e.,
|
||||
% branches of each subtree differ in depth by at most 1.
|
||||
|
||||
is_assoc(Assoc) :-
|
||||
is_assoc(Assoc, _Min, _Max, _Depth).
|
||||
@@ -151,12 +150,10 @@ balance(=,-).
|
||||
balance(<,<).
|
||||
balance(>,>).
|
||||
|
||||
%! gen_assoc(?Key, +Assoc, ?Value) is nondet.
|
||||
%% gen_assoc(?Key, +Assoc, ?Value) is nondet.
|
||||
%
|
||||
% True if Key-Value is an association in Assoc. Enumerates keys in
|
||||
% ascending order on backtracking.
|
||||
%
|
||||
% @see get_assoc/3.
|
||||
% True if Key-Value is an association in Assoc. Enumerates keys in
|
||||
% ascending order on backtracking.
|
||||
|
||||
gen_assoc(Key, Assoc, Value) :-
|
||||
( ground(Key)
|
||||
@@ -171,11 +168,11 @@ gen_assoc_(Key, t(_,_,_,_,R), Val) :-
|
||||
gen_assoc_(Key, R, Val).
|
||||
|
||||
|
||||
%! get_assoc(+Key, +Assoc, -Value) is semidet.
|
||||
%% get_assoc(+Key, +Assoc, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is an association in Assoc.
|
||||
% True if Key-Value is an association in Assoc.
|
||||
%
|
||||
% @error type_error(assoc, Assoc) if Assoc is not an association list.
|
||||
% Throws error: `type_error(assoc, Assoc)` if Assoc is not an association list.
|
||||
|
||||
get_assoc(Key, Assoc, Val) :-
|
||||
must_be(assoc, Assoc),
|
||||
@@ -201,9 +198,9 @@ get_assoc(>, Key, _, _, Tree, Val) :-
|
||||
% :- endif.
|
||||
|
||||
|
||||
%! get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
|
||||
%% get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
|
||||
%
|
||||
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
|
||||
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
|
||||
|
||||
get_assoc(Key, t(K,V,B,L,R), Val, t(K,NV,B,NL,NR), NVal) :-
|
||||
compare(Rel, Key, K),
|
||||
@@ -216,12 +213,12 @@ get_assoc(>, Key, V, L, R, Val, V, L, NR, NVal) :-
|
||||
get_assoc(Key, R, Val, NR, NVal).
|
||||
|
||||
|
||||
%! list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%% list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%
|
||||
% Create an association from a list Pairs of Key-Value pairs. List
|
||||
% must not contain duplicate keys.
|
||||
% Create an association from a list Pairs of Key-Value pairs. List
|
||||
% must not contain duplicate keys.
|
||||
%
|
||||
% @error domain_error(unique_key_pairs, List) if List contains duplicate keys
|
||||
% Throws error: `domain_error(unique_key_pairs, List)` if List contains duplicate keys
|
||||
|
||||
list_to_assoc(List, Assoc) :-
|
||||
( List = [] -> Assoc = t
|
||||
@@ -246,13 +243,13 @@ list_to_assoc(N, List, More, Depth, t(K,V,Balance,L,R)) :-
|
||||
compare(B, RDepth, LDepth),
|
||||
balance(B, Balance).
|
||||
|
||||
%! ord_list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%% ord_list_to_assoc(+Pairs, -Assoc) is det.
|
||||
%
|
||||
% Assoc is created from an ordered list Pairs of Key-Value
|
||||
% pairs. The pairs must occur in strictly ascending order of
|
||||
% their keys.
|
||||
% Assoc is created from an ordered list Pairs of Key-Value
|
||||
% pairs. The pairs must occur in strictly ascending order of
|
||||
% their keys.
|
||||
%
|
||||
% @error domain_error(key_ordered_pairs, List) if pairs are not ordered.
|
||||
% Throws error: `domain_error(key_ordered_pairs, List)` if pairs are not ordered.
|
||||
|
||||
ord_list_to_assoc(Sorted, Assoc) :-
|
||||
( Sorted = [] -> Assoc = t
|
||||
@@ -263,9 +260,9 @@ ord_list_to_assoc(Sorted, Assoc) :-
|
||||
)
|
||||
).
|
||||
|
||||
%! ord_pairs(+Pairs) is semidet
|
||||
%% ord_pairs(+Pairs) is semidet
|
||||
%
|
||||
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
|
||||
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
|
||||
|
||||
ord_pairs([K-_V|Rest]) :-
|
||||
ord_pairs(Rest, K).
|
||||
@@ -274,9 +271,9 @@ ord_pairs([K-_V|Rest], K0) :-
|
||||
K0 @< K,
|
||||
ord_pairs(Rest, K).
|
||||
|
||||
%! map_assoc(:Pred, +Assoc) is semidet.
|
||||
%% map_assoc(:Pred, +Assoc) is semidet.
|
||||
%
|
||||
% True if Pred(Value) is true for all values in Assoc.
|
||||
% True if Pred(Value) is true for all values in Assoc.
|
||||
|
||||
map_assoc(Pred, T) :-
|
||||
map_assoc_(T, Pred).
|
||||
@@ -287,10 +284,10 @@ map_assoc_(t(_,Val,_,L,R), Pred) :-
|
||||
call(Pred, Val),
|
||||
map_assoc_(R, Pred).
|
||||
|
||||
%! map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
|
||||
%% map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
|
||||
%
|
||||
% Map corresponding values. True if Assoc is Assoc0 with Pred
|
||||
% applied to all corresponding pairs of of values.
|
||||
% Map corresponding values. True if Assoc is Assoc0 with Pred
|
||||
% applied to all corresponding pairs of of values.
|
||||
|
||||
map_assoc(Pred, T0, T) :-
|
||||
map_assoc_(T0, Pred, T).
|
||||
@@ -302,9 +299,9 @@ map_assoc_(t(Key,Val,B,L0,R0), Pred, t(Key,Ans,B,L1,R1)) :-
|
||||
map_assoc_(R0, Pred, R1).
|
||||
|
||||
|
||||
%! max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%% max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc and Key is the largest key.
|
||||
% True if Key-Value is in Assoc and Key is the largest key.
|
||||
|
||||
max_assoc(t(K,V,_,_,R), Key, Val) :-
|
||||
max_assoc(R, K, V, Key, Val).
|
||||
@@ -314,9 +311,9 @@ max_assoc(t(K,V,_,_,R), _, _, Key, Val) :-
|
||||
max_assoc(R, K, V, Key, Val).
|
||||
|
||||
|
||||
%! min_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%% min_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is in assoc and Key is the smallest key.
|
||||
% True if Key-Value is in assoc and Key is the smallest key.
|
||||
|
||||
min_assoc(t(K,V,_,L,_), Key, Val) :-
|
||||
min_assoc(L, K, V, Key, Val).
|
||||
@@ -326,10 +323,10 @@ min_assoc(t(K,V,_,L,_), _, _, Key, Val) :-
|
||||
min_assoc(L, K, V, Key, Val).
|
||||
|
||||
|
||||
%! put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
|
||||
%% put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
|
||||
%
|
||||
% Assoc is Assoc0, except that Key is associated with
|
||||
% Value. This can be used to insert and change associations.
|
||||
% Assoc is Assoc0, except that Key is associated with
|
||||
% Value. This can be used to insert and change associations.
|
||||
|
||||
put_assoc(Key, A0, Value, A) :-
|
||||
insert(A0, Key, Value, A, _).
|
||||
@@ -361,11 +358,11 @@ table(< , right , - , no , no ) :- !.
|
||||
table(> , left , - , no , no ) :- !.
|
||||
table(> , right , - , no , yes ) :- !.
|
||||
|
||||
%! del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%% del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc0 and Key is the smallest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
% True if Key-Value is in Assoc0 and Key is the smallest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
|
||||
del_min_assoc(Tree, Key, Val, NewTree) :-
|
||||
del_min_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
||||
@@ -375,11 +372,11 @@ del_min_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
|
||||
del_min_assoc(L, Key, Val, NewL, LeftChanged),
|
||||
deladjust(LeftChanged, t(K,V,B,NewL,R), left, NewTree, Changed).
|
||||
|
||||
%! del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%% del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc0 and Key is the greatest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
% True if Key-Value is in Assoc0 and Key is the greatest key.
|
||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||
|
||||
del_max_assoc(Tree, Key, Val, NewTree) :-
|
||||
del_max_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
||||
@@ -389,10 +386,10 @@ del_max_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
|
||||
del_max_assoc(R, Key, Val, NewR, RightChanged),
|
||||
deladjust(RightChanged, t(K,V,B,L,NewR), right, NewTree, Changed).
|
||||
|
||||
%! del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
|
||||
%% del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
|
||||
% Key-Value removed.
|
||||
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
|
||||
% Key-Value removed.
|
||||
|
||||
del_assoc(Key, A0, Value, A) :-
|
||||
delete(A0, Key, Value, A, _).
|
||||
|
||||
+10
-85
@@ -19,77 +19,12 @@
|
||||
'$default_attr_list'(PGs, Module, AttrVar).
|
||||
'$default_attr_list'([], _, _) --> [].
|
||||
|
||||
'$absent_attr'(V, Attr) :-
|
||||
'$get_attr_list'(V, Ls),
|
||||
'$absent_from_list'(Ls, Attr).
|
||||
|
||||
'$absent_from_list'(X, Attr) :-
|
||||
( var(X) ->
|
||||
true
|
||||
; X = [L|Ls],
|
||||
L \= Attr ->
|
||||
'$absent_from_list'(Ls, Attr)
|
||||
).
|
||||
|
||||
'$get_attr'(V, Attr) :-
|
||||
'$get_attr_list'(V, Ls),
|
||||
nonvar(Ls),
|
||||
'$get_from_list'(Ls, V, Attr).
|
||||
|
||||
'$get_from_list'([L|Ls], V, Attr) :-
|
||||
nonvar(L),
|
||||
( L \= Attr ->
|
||||
nonvar(Ls),
|
||||
'$get_from_list'(Ls, V, Attr)
|
||||
; L = Attr,
|
||||
'$enqueue_attr_var'(V)
|
||||
).
|
||||
|
||||
'$put_attr'(V, Attr) :-
|
||||
'$get_attr_list'(V, Ls),
|
||||
'$add_to_list'(Ls, V, Attr).
|
||||
|
||||
'$add_to_list'(Ls, V, Attr) :-
|
||||
( var(Ls) ->
|
||||
Ls = [Attr | _],
|
||||
'$enqueue_attr_var'(V)
|
||||
; Ls = [_ | Ls0],
|
||||
'$add_to_list'(Ls0, V, Attr)
|
||||
).
|
||||
|
||||
'$del_attr'(Ls0, _, _) :-
|
||||
var(Ls0),
|
||||
!.
|
||||
'$del_attr'(Ls0, V, Attr) :-
|
||||
Ls0 = [Att | Ls1],
|
||||
nonvar(Att),
|
||||
( Att \= Attr ->
|
||||
'$del_attr_buried'(Ls0, Ls1, V, Attr)
|
||||
; '$enqueue_attr_var'(V),
|
||||
'$del_attr_head'(V),
|
||||
'$del_attr'(Ls1, V, Attr)
|
||||
).
|
||||
|
||||
'$del_attr_step'(Ls1, V, Attr) :-
|
||||
( nonvar(Ls1) ->
|
||||
Ls1 = [_ | Ls2],
|
||||
'$del_attr_buried'(Ls1, Ls2, V, Attr)
|
||||
'$absent_attr'(V, Module, Attr) :-
|
||||
( '$get_from_attr_list'(V, Module, Attr) ->
|
||||
false
|
||||
; true
|
||||
).
|
||||
|
||||
%% assumptions: Ls0 is a list, Ls1 is its tail;
|
||||
%% the head of Ls0 can be ignored.
|
||||
'$del_attr_buried'(Ls0, Ls1, V, Attr) :-
|
||||
( var(Ls1) -> true
|
||||
; Ls1 = [Att | Ls2] ->
|
||||
( Att \= Attr ->
|
||||
'$del_attr_buried'(Ls1, Ls2, V, Attr)
|
||||
; '$enqueue_attr_var'(V),
|
||||
'$del_attr_non_head'(Ls0), %% set tail of Ls0 = tail of Ls1. can be undone by backtracking.
|
||||
'$del_attr_step'(Ls1, V, Attr)
|
||||
)
|
||||
).
|
||||
|
||||
'$copy_attr_list'(L, _Module, []) :- var(L), !.
|
||||
'$copy_attr_list'([Module0:Att|Atts], Module, CopiedAtts) :-
|
||||
( Module0 == Module ->
|
||||
@@ -145,38 +80,28 @@ put_attr(Name, Arity, Module) -->
|
||||
{ functor(Attr, Name, Arity) },
|
||||
[(put_atts(V, +Attr) :-
|
||||
!,
|
||||
functor(Attr, Head, Arity),
|
||||
functor(AttrForm, Head, Arity),
|
||||
'$get_attr_list'(V, Ls),
|
||||
atts:'$del_attr'(Ls, V, Module:AttrForm),
|
||||
atts:'$put_attr'(V, Module:Attr)),
|
||||
(put_atts(V, Attr) :-
|
||||
'$put_to_attr_list'(V, Module, Attr)),
|
||||
(put_atts(V, Attr) :-
|
||||
!,
|
||||
functor(Attr, Head, Arity),
|
||||
functor(AttrForm, Head, Arity),
|
||||
'$get_attr_list'(V, Ls),
|
||||
atts:'$del_attr'(Ls, V, Module:AttrForm),
|
||||
atts:'$put_attr'(V, Module:Attr)),
|
||||
'$put_to_attr_list'(V, Module, Attr)),
|
||||
(put_atts(V, -Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
'$get_attr_list'(V, Ls),
|
||||
atts:'$del_attr'(Ls, V, Module:Attr))].
|
||||
'$del_from_attr_list'(V, Module, Attr))].
|
||||
|
||||
get_attr(Name, Arity, Module) -->
|
||||
{ functor(Attr, Name, Arity) },
|
||||
[(get_atts(V, +Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
atts:'$get_attr'(V, Module:Attr)),
|
||||
atts:'$get_from_attr_list'(V, Module, Attr)),
|
||||
(get_atts(V, Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
atts:'$get_attr'(V, Module:Attr)),
|
||||
atts:'$get_from_attr_list'(V, Module, Attr)),
|
||||
(get_atts(V, -Attr) :-
|
||||
!,
|
||||
functor(Attr, _, _),
|
||||
atts:'$absent_attr'(V, Module:Attr))].
|
||||
atts:'$absent_attr'(V, Module, Attr))].
|
||||
|
||||
user:goal_expansion(Term, M:put_atts(Var, Attr)) :-
|
||||
nonvar(Term),
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/** Predicates that generate integers
|
||||
|
||||
These predicates can be used to reason about integers in a reduced domain that
|
||||
follow some property. `library(clpz)` provides another way of reasoning about
|
||||
integers that may also be interesting.
|
||||
*/
|
||||
|
||||
:- module(between, [between/3, gen_int/1, gen_nat/1, numlist/2, numlist/3, repeat/1]).
|
||||
|
||||
%% TODO: numlist/5.
|
||||
@@ -5,6 +12,24 @@
|
||||
:- use_module(library(lists), [length/2]).
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% between(+Lower, +Upper, -X).
|
||||
%
|
||||
% Given Lower and Upper are both integer numbers, true iff X is an integer so that _Lower =< X =< Upper_.
|
||||
% Can be used both to check if X is between Lower and Upper or to generate an integer between
|
||||
% Lower and Upper.
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ?- between(10, 20, 15).
|
||||
% true.
|
||||
% ?- between(10, 20, 25).
|
||||
% false.
|
||||
% ?- between(3, 5, X).
|
||||
% X = 3
|
||||
% ; X = 4
|
||||
% ; X = 5.
|
||||
% ```
|
||||
between(Lower, Upper, X) :-
|
||||
must_be(integer, Lower),
|
||||
must_be(integer, Upper),
|
||||
@@ -30,6 +55,9 @@ enumerate_nats(I0, N) :-
|
||||
I1 is I0 + 1,
|
||||
enumerate_nats(I1, N).
|
||||
|
||||
%% gen_nat(?N)
|
||||
%
|
||||
% True iff N is a natural number.
|
||||
gen_nat(N) :-
|
||||
can_be(integer, N),
|
||||
( var(N) -> enumerate_nats(0, N)
|
||||
@@ -44,6 +72,9 @@ enumerate_ints(I0, N) :-
|
||||
I1 is I0 + 1,
|
||||
enumerate_ints(I1, N).
|
||||
|
||||
%% gen_int(?N)
|
||||
%
|
||||
% True iff N is an integer.
|
||||
gen_int(N) :-
|
||||
can_be(integer, N),
|
||||
( var(N) -> enumerate_ints(0, N)
|
||||
@@ -55,9 +86,24 @@ repeat_integer(N) :-
|
||||
repeat_integer(N0) :-
|
||||
N0 > 0, N1 is N0 - 1, repeat_integer(N1).
|
||||
|
||||
%% repeat(+N)
|
||||
%
|
||||
% Succeeds N times. This predicate is only included for compatibility and *should not be used*
|
||||
% because it lacks a declarative interpretation.
|
||||
repeat(N) :-
|
||||
must_be(integer, N), repeat_integer(N).
|
||||
|
||||
%% numlist(?Upper, ?List)
|
||||
%
|
||||
% True iff List is the list of integers _[1, ..., Upper]_. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- numlist(X, Y).
|
||||
% X = 1, Y = [1],
|
||||
% ; X = 2, Y = [1,2]
|
||||
% ; X = 3, Y = [1,2,3]
|
||||
% ; ... .
|
||||
% ```
|
||||
numlist(Upper, List) :-
|
||||
( integer(Upper) -> findall(X, between(1, Upper, X), List)
|
||||
; List = [_|_], length(List, Upper), findall(X, between(1, Upper, X), List)
|
||||
@@ -106,5 +152,14 @@ gen_ints(L, U) :-
|
||||
),
|
||||
L =< U.
|
||||
|
||||
%% numlist(?Lower, ?Upper, ?List).
|
||||
%
|
||||
% True iff List is a list of the form _[Lower, ..., Upper]_.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- numlist(5, 10, X).
|
||||
% X = [5,6,7,8,9,10].
|
||||
% ```
|
||||
numlist(Lower, Upper, List) :-
|
||||
gen_ints(Lower, Upper), findall(X, between(Lower, Upper, X), List).
|
||||
|
||||
+775
-211
File diff suppressed because it is too large
Load Diff
+163
-37
@@ -1,9 +1,18 @@
|
||||
/** High-level predicates to work with chars and strings
|
||||
|
||||
This module contains predicates that relates strings of chars
|
||||
to other representations, as well as high-level predicates to
|
||||
read and write chars.
|
||||
|
||||
*/
|
||||
|
||||
:- module(charsio, [char_type/2,
|
||||
chars_utf8bytes/2,
|
||||
get_single_char/1,
|
||||
get_n_chars/3,
|
||||
read_line_to_chars/3,
|
||||
get_line_to_chars/3,
|
||||
read_from_chars/2,
|
||||
read_term_from_chars/3,
|
||||
write_term_to_chars/3,
|
||||
chars_base64/3]).
|
||||
|
||||
@@ -65,6 +74,63 @@ extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
|
||||
).
|
||||
|
||||
|
||||
%% char_type(+Char, -Type).
|
||||
%
|
||||
% Given a Char, Type is one of the categories that char fits in.
|
||||
% Possible categories are:
|
||||
%
|
||||
% - `alnum`
|
||||
% - `alpha`
|
||||
% - `alphabetic`
|
||||
% - `alphanumeric`
|
||||
% - `ascii`
|
||||
% - `ascii_graphic`
|
||||
% - `ascii_punctuation`
|
||||
% - `binary_digit`
|
||||
% - `control`
|
||||
% - `decimal_digit`
|
||||
% - `exponent`
|
||||
% - `graphic`
|
||||
% - `graphic_token`
|
||||
% - `hexadecimal_digit`
|
||||
% - `layout`
|
||||
% - `lower`
|
||||
% - `meta`
|
||||
% - `numeric`
|
||||
% - `octal_digit`
|
||||
% - `octet`
|
||||
% - `prolog`
|
||||
% - `sign`
|
||||
% - `solo`
|
||||
% - `symbolic_control`
|
||||
% - `symbolic_hexadecimal`
|
||||
% - `upper`
|
||||
% - `to_lower(Lower)`
|
||||
% - `to_upper(Upper)`
|
||||
% - `whitespace`
|
||||
%
|
||||
% An example:
|
||||
%
|
||||
% ```
|
||||
% ?- char_type(a, Type).
|
||||
% Type = alnum
|
||||
% ; Type = alpha
|
||||
% ; Type = alphabetic
|
||||
% ; Type = alphanumeric
|
||||
% ; Type = ascii
|
||||
% ; Type = ascii_graphic
|
||||
% ; Type = hexadecimal_digit
|
||||
% ; Type = lower
|
||||
% ; Type = octet
|
||||
% ; Type = prolog
|
||||
% ; Type = symbolic_control
|
||||
% ; Type = to_lower("a")
|
||||
% ; Type = to_upper("A")
|
||||
% ; false.
|
||||
% ```
|
||||
%
|
||||
% Note that uppercase and lowercase transformations use a string. This is because
|
||||
% some characters do not map 1:1 between lowercase and uppercase.
|
||||
char_type(Char, Type) :-
|
||||
must_be(character, Char),
|
||||
( ground(Type) ->
|
||||
@@ -102,27 +168,68 @@ ctype(sign).
|
||||
ctype(solo).
|
||||
ctype(symbolic_control).
|
||||
ctype(symbolic_hexadecimal).
|
||||
ctype(to_lower(_)).
|
||||
ctype(to_upper(_)).
|
||||
ctype(upper).
|
||||
ctype(whitespace).
|
||||
|
||||
|
||||
%% get_single_char(-Char).
|
||||
%
|
||||
% Gets a single char from the current input stream.
|
||||
get_single_char(C) :-
|
||||
( var(C) -> '$get_single_char'(C)
|
||||
; atom_length(C, 1) -> '$get_single_char'(C)
|
||||
; type_error(in_character, C, get_single_char/1)
|
||||
).
|
||||
|
||||
|
||||
%% read_from_chars(+Chars, -Term).
|
||||
%
|
||||
% Given a string made of chars which contains a representation of
|
||||
% a Prolog term, Term is the Prolog term represented. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- read_from_chars("f(x,y).", X).
|
||||
% X = f(x,y).
|
||||
% ```
|
||||
read_from_chars(Chars, Term) :-
|
||||
must_be(chars, Chars),
|
||||
'$read_term_from_chars'(Chars, Term).
|
||||
must_be(var, Term),
|
||||
'$read_from_chars'(Chars, Term).
|
||||
|
||||
%% read_term_from_chars(+Chars, -Term, +Options).
|
||||
%
|
||||
% Like `read_from_chars`, except the reader is configured according to
|
||||
% `Options` which are those of `read_term`.
|
||||
%
|
||||
% ```
|
||||
% ?- read_term_from_chars("f(X,y).", T, [variable_names(['X'=X])]).
|
||||
% T = f(X,y).
|
||||
% ```
|
||||
read_term_from_chars(Chars, Term, Options) :-
|
||||
must_be(chars, Chars),
|
||||
must_be(var, Term),
|
||||
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
|
||||
'$read_term_from_chars'(Chars, Term, Singletons, Variables, VariableNames).
|
||||
|
||||
%% write_term_to_chars(+Term, +Options, -Chars).
|
||||
%
|
||||
% Given a Term which is a Prolog term and a set of options, Chars is
|
||||
% string representation of that term. Options available are:
|
||||
%
|
||||
% * `ignore_ops(+Boolean)` if `true`, the generic term representation is used everywhere. In `false`
|
||||
% (default), operators do not use that generic term representation.
|
||||
% * `max_depth(+N)` if the term is nested deeper than N, print the reminder as ellipses.
|
||||
% If N = 0 (default), there's no limit.
|
||||
% * `numbervars(+Boolean)` if true, replaces `$VAR(N)` variables with letters, in order. Default is false.
|
||||
% * `quoted(+Boolean)` if true, strings and atoms that need quotes to be valid Prolog syntax, are quoted. Default is false.
|
||||
% * `variable_names(+List)` assign names to variables in term. List should be a list of terms of format `Name=Var`.
|
||||
% * `double_quotes(+Boolean)` if true, strings are printed in double quotes rather than with list notation. Default is false.
|
||||
write_term_to_chars(_, Options, _) :-
|
||||
var(Options), instantiation_error(write_term_to_chars/3).
|
||||
write_term_to_chars(Term, Options, Chars) :-
|
||||
builtins:parse_write_options(Options,
|
||||
[IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
|
||||
[DoubleQuotes, IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
|
||||
write_term_to_chars/3),
|
||||
( nonvar(Chars) ->
|
||||
throw(error(uninstantiation_error(Chars), write_term_to_chars/3))
|
||||
@@ -131,7 +238,7 @@ write_term_to_chars(Term, Options, Chars) :-
|
||||
),
|
||||
term_variables(Term, Vars),
|
||||
extend_var_list(Vars, VNNames, NewVarNames, numbervars),
|
||||
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth).
|
||||
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth, DoubleQuotes).
|
||||
|
||||
% Encodes Ch character to list of Bytes.
|
||||
char_utf8bytes(Ch, Bytes) :-
|
||||
@@ -151,6 +258,17 @@ encode(Code, Prefix, Nb) -->
|
||||
% Maps characters and UTF-8 bytes.
|
||||
% If Cs is a variable, parses Bs as a list of UTF-8 bytes.
|
||||
% Otherwise, transform the list of characters Cs to UTF-8 bytes.
|
||||
|
||||
%% chars_utf8bytes(?Chars, ?Bytes).
|
||||
%
|
||||
% Maps a string made of chars with a list of UTF-8 bytes. Some examples:
|
||||
%
|
||||
% ```
|
||||
% ?- chars_utf8bytes("Prolog", X).
|
||||
% X = [80,114,111,108,111,103].
|
||||
% ?- chars_utf8bytes(X, [226, 136, 145]).
|
||||
% X = "∑".
|
||||
% ```
|
||||
chars_utf8bytes(Cs, Bs) :-
|
||||
var(Cs), must_be(list, Bs) ->
|
||||
once(phrase(decode_utf8(Cs), Bs))
|
||||
@@ -177,58 +295,66 @@ continuation(Code, Chars, Nb) --> [Byte],
|
||||
% each remaining continuation byte (if any) will raise 0xFFFD too
|
||||
continuation(_, ['\xFFFD\'|T], _) --> [_], decode_utf8(T).
|
||||
|
||||
|
||||
read_line_to_chars(Stream, Cs0, Cs) :-
|
||||
%% get_line_to_chars(+Stream, -Chars, +InitialChars).
|
||||
%
|
||||
% Reads chars from stream Stream until it finds a `\n` character.
|
||||
% InitialChars will be appended at the end of Chars
|
||||
get_line_to_chars(Stream, Cs0, Cs) :-
|
||||
'$get_n_chars'(Stream, 1, Char), % this also works for binary streams
|
||||
( Char == [] -> Cs0 = Cs
|
||||
; Char = [C],
|
||||
Cs0 = [C|Rest],
|
||||
( C == '\n' -> Rest = Cs
|
||||
; read_line_to_chars(Stream, Rest, Cs)
|
||||
; get_line_to_chars(Stream, Rest, Cs)
|
||||
)
|
||||
).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Read N characters from Stream.
|
||||
|
||||
If N is a variable, read until EOF, unifying N with the number of
|
||||
characters read.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% get_n_chars(+Stream, ?N, -Chars).
|
||||
%
|
||||
% Read N chars from stream Stream. N can be an integer, in that case
|
||||
% only N chars are read, or a variable, unifying N with the number of chars
|
||||
% read until it found EOF.
|
||||
get_n_chars(Stream, N, Cs) :-
|
||||
can_be(integer, N),
|
||||
( var(N) ->
|
||||
read_to_eof(Stream, Cs),
|
||||
get_to_eof(Stream, Cs),
|
||||
length(Cs, N)
|
||||
; N >= 0,
|
||||
'$get_n_chars'(Stream, N, Cs)
|
||||
).
|
||||
|
||||
read_to_eof(Stream, Cs) :-
|
||||
'$get_n_chars'(Stream, 512, Cs0),
|
||||
get_n_chars_wrapper(Stream, N, Cs) :-
|
||||
'$get_n_chars'(Stream, N, Cs).
|
||||
|
||||
get_to_eof(Stream, Cs) :-
|
||||
catch(get_n_chars_wrapper(Stream, 512, Cs0),
|
||||
error(syntax_error(unexpected_end_of_file), _),
|
||||
Cs0 = []),
|
||||
( Cs0 == [] -> Cs = []
|
||||
; partial_string(Cs0, Cs, Rest),
|
||||
read_to_eof(Stream, Rest)
|
||||
get_to_eof(Stream, Rest)
|
||||
).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Relation between a list of characters Cs and its Base64 encoding Bs,
|
||||
also a list of characters.
|
||||
|
||||
At least one of the arguments must be instantiated.
|
||||
|
||||
Options are:
|
||||
|
||||
- padding(Boolean)
|
||||
Whether to use padding: true (the default) or false.
|
||||
- charset(C)
|
||||
Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
|
||||
|
||||
Example:
|
||||
|
||||
?- chars_base64("hello", Bs, []).
|
||||
Bs = "aGVsbG8=".
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% chars_base64(?Chars, ?Base64, +Options).
|
||||
%
|
||||
% Relation between a list of characters Cs and its Base64 encoding Bs,
|
||||
% also a list of characters.
|
||||
%
|
||||
% At least one of the arguments must be instantiated.
|
||||
%
|
||||
% Options are:
|
||||
%
|
||||
% - `padding(Boolean)`
|
||||
% Whether to use padding: true (the default) or false.
|
||||
% - `charset(C)`
|
||||
% Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- chars_base64("hello", Bs, []).
|
||||
% Bs = "aGVsbG8=".
|
||||
% ```
|
||||
|
||||
chars_base64(Cs, Bs, Options) :-
|
||||
must_be(list, Options),
|
||||
|
||||
+273
-13
@@ -1,6 +1,6 @@
|
||||
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
||||
|
||||
Copyright (C): 2019 Markus Triska
|
||||
Copyright (C): 2019-2023 Markus Triska
|
||||
All rights reserved.
|
||||
|
||||
E-mail: triska@metalevel.at
|
||||
@@ -105,6 +105,262 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
|
||||
Access =.. [Module,_].
|
||||
|
||||
|
||||
/** Constraint Logic Programming over Boolean variables
|
||||
|
||||
## Introduction
|
||||
|
||||
This library provides CLP(B), Constraint Logic Programming over
|
||||
Boolean variables. It can be used to model and solve combinatorial
|
||||
problems such as verification, allocation and covering tasks.
|
||||
|
||||
CLP(B) is an instance of the general CLP(_X_) scheme,
|
||||
extending logic programming with reasoning over specialised domains.
|
||||
|
||||
The implementation is based on reduced and ordered Binary Decision
|
||||
Diagrams (BDDs).
|
||||
|
||||
Benchmarks and usage examples of this library are available from:
|
||||
[*https://www.metalevel.at/clpb/*](https://www.metalevel.at/clpb/)
|
||||
|
||||
## Boolean expressions
|
||||
|
||||
A _Boolean expression_ is one of:
|
||||
|
||||
| `0` | false |
|
||||
| `1` | true |
|
||||
| _variable_ | unknown truth value |
|
||||
| _atom_ | universally quantified variable |
|
||||
| ~ _Expr_ | logical NOT |
|
||||
| _Expr_ + _Expr_ | logical OR |
|
||||
| _Expr_ * _Expr_ | logical AND |
|
||||
| _Expr_ # _Expr_ | exclusive OR |
|
||||
| _Var_ ^ _Expr_ | existential quantification |
|
||||
| _Expr_ =:= _Expr_ | equality |
|
||||
| _Expr_ =\= _Expr_ | disequality (same as #) |
|
||||
| _Expr_ =< _Expr_ | less or equal (implication) |
|
||||
| _Expr_ >= _Expr_ | greater or equal |
|
||||
| _Expr_ < _Expr_ | less than |
|
||||
| _Expr_ > _Expr_ | greater than |
|
||||
| card(Is,Exprs) | cardinality constraint (_see below_) |
|
||||
| `+(Exprs)` | n-fold disjunction (_see below_) |
|
||||
| `*(Exprs)` | n-fold conjunction (_see below_) |
|
||||
|
||||
where _Expr_ again denotes a Boolean expression.
|
||||
|
||||
The Boolean expression `card(Is,Exprs)` is true iff the number of true
|
||||
expressions in the list `Exprs` is a member of the list `Is` of
|
||||
integers and integer ranges of the form `From-To`. For example, to
|
||||
state that precisely two of the three variables `X`, `Y` and `Z` are
|
||||
`true`, you can use `sat(card([2],[X,Y,Z]))`.
|
||||
|
||||
`+(Exprs)` and `*(Exprs)` denote, respectively, the disjunction and
|
||||
conjunction of all elements in the list `Exprs` of Boolean
|
||||
expressions.
|
||||
|
||||
Atoms denote parametric values that are universally quantified. All
|
||||
universal quantifiers appear implicitly in front of the entire
|
||||
expression. In residual goals, universally quantified variables always
|
||||
appear on the right-hand side of equations. Therefore, they can be
|
||||
used to express functional dependencies on input variables.
|
||||
|
||||
## Interface predicates
|
||||
|
||||
The most frequently used CLP(B) predicates are:
|
||||
|
||||
* `sat(+Expr)`
|
||||
True iff the Boolean expression Expr is satisfiable.
|
||||
|
||||
* `taut(+Expr, -T)`
|
||||
If Expr is a tautology with respect to the posted constraints, succeeds
|
||||
with *T = 1*. If Expr cannot be satisfied, succeeds with *T = 0*.
|
||||
Otherwise, it fails.
|
||||
|
||||
* `labeling(+Vs)`
|
||||
Assigns truth values to the variables Vs such that all constraints
|
||||
are satisfied.
|
||||
|
||||
The unification of a CLP(B) variable _X_ with a term _T_ is equivalent
|
||||
to posting the constraint sat(X=:=T).
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example session with a few queries and their answers:
|
||||
|
||||
```
|
||||
?- use_module(library(clpb)).
|
||||
true.
|
||||
|
||||
?- sat(X*Y).
|
||||
X = 1, Y = 1.
|
||||
|
||||
?- sat(X * ~X).
|
||||
false.
|
||||
|
||||
?- taut(X * ~X, T).
|
||||
T = 0, clpb:sat(X=:=X).
|
||||
|
||||
?- sat(X^Y^(X+Y)).
|
||||
clpb:sat(X=:=X), clpb:sat(Y=:=Y).
|
||||
|
||||
?- sat(X*Y + X*Z), labeling([X,Y,Z]).
|
||||
X = 1, Y = 0, Z = 1
|
||||
; X = 1, Y = 1, Z = 0
|
||||
; X = 1, Y = 1, Z = 1.
|
||||
|
||||
?- sat(X =< Y), sat(Y =< Z), taut(X =< Z, T).
|
||||
T = 1, clpb:sat(X=:=X*Y), clpb:sat(Y=:=Y*Z).
|
||||
|
||||
?- sat(1#X#a#b).
|
||||
clpb:sat(X=:=a#b).
|
||||
```
|
||||
|
||||
The pending residual goals constrain remaining variables to Boolean
|
||||
expressions and are declaratively equivalent to the original query.
|
||||
The last example illustrates that when applicable, remaining variables
|
||||
are expressed as functions of universally quantified variables.
|
||||
|
||||
## Obtaining BDDs
|
||||
|
||||
By default, CLP(B) residual goals appear in (approximately) algebraic
|
||||
normal form (ANF). This projection is often computationally expensive.
|
||||
We can assert `clpb:clpb_residuals(bdd)` to see the BDD representation
|
||||
of all constraints. This results in faster projection to residual
|
||||
goals, and is also useful for learning more about BDDs. For example:
|
||||
|
||||
```
|
||||
?- asserta(clpb:clpb_residuals(bdd)).
|
||||
true.
|
||||
|
||||
?- sat(X#Y).
|
||||
node(3)- (v(X, 0)->node(2);node(1)),
|
||||
node(1)- (v(Y, 1)->true;false),
|
||||
node(2)- (v(Y, 1)->false;true).
|
||||
```
|
||||
|
||||
Note that this representation cannot be pasted back on the toplevel,
|
||||
and its details are subject to change. Use copy_term/3 to obtain
|
||||
such answers as Prolog terms.
|
||||
|
||||
The variable order of the BDD is determined by the order in which the
|
||||
variables first appear in constraints. To obtain different orders,
|
||||
we can for example use:
|
||||
|
||||
```
|
||||
?- sat(+[1,Y,X]), sat(X#Y).
|
||||
node(3)- (v(Y, 0)->node(2);node(1)),
|
||||
node(1)- (v(X, 1)->true;false),
|
||||
node(2)- (v(X, 1)->false;true).
|
||||
```
|
||||
|
||||
## Enabling monotonic CLP(B)
|
||||
|
||||
In the default execution mode, CLP(B) constraints are _not_ monotonic.
|
||||
This means that _adding_ constraints can yield new solutions. For
|
||||
example:
|
||||
|
||||
```
|
||||
?- sat(X=:=1), X = 1+0.
|
||||
false.
|
||||
|
||||
?- X = 1+0, sat(X=:=1), X = 1+0.
|
||||
X = 1+0.
|
||||
```
|
||||
|
||||
This behaviour is highly problematic from a logical point of view, and
|
||||
it may render [*declarative
|
||||
debugging*](https://www.metalevel.at/prolog/debugging)
|
||||
techniques inapplicable.
|
||||
|
||||
Assert `clpb:monotonic` to make CLP(B) *monotonic*. If this mode is
|
||||
enabled, then you must wrap CLP(B) variables with the functor
|
||||
`v/1`. For example:
|
||||
|
||||
```
|
||||
?- asserta(clpb:monotonic).
|
||||
true.
|
||||
|
||||
?- sat(v(X)=:=1#1).
|
||||
X = 0.
|
||||
```
|
||||
|
||||
## Example: Pigeons
|
||||
|
||||
In this example, we are attempting to place _I_ pigeons into _J_ holes
|
||||
in such a way that each hole contains at most one pigeon. One
|
||||
interesting property of this task is that it can be formulated using
|
||||
only _cardinality constraints_ (`card/2`). Another interesting aspect
|
||||
is that this task has no short resolution refutations in general.
|
||||
|
||||
In the following, we use [*Prolog DCG
|
||||
notation*](https://www.metalevel.at/prolog/dcg) to describe a
|
||||
list `Cs` of CLP(B) constraints that must all be satisfied.
|
||||
|
||||
```
|
||||
:- use_module(library(clpb)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
pigeon(I, J, Rows, Cs) :-
|
||||
length(Rows, I), length(Row, J),
|
||||
maplist(same_length(Row), Rows),
|
||||
transpose(Rows, TRows),
|
||||
phrase((all_cards(Rows,[1]),all_cards(TRows,[0,1])), Cs).
|
||||
|
||||
all_cards([], _) --> [].
|
||||
all_cards([Ls|Lss], Cs) --> [card(Cs,Ls)], all_cards(Lss, Cs).
|
||||
```
|
||||
|
||||
Example queries:
|
||||
|
||||
```
|
||||
?- pigeon(9, 8, Rows, Cs), sat(*(Cs)).
|
||||
false.
|
||||
|
||||
?- pigeon(2, 3, Rows, Cs), sat(*(Cs)),
|
||||
append(Rows, Vs), labeling(Vs),
|
||||
maplist(portray_clause, Rows).
|
||||
[0,0,1].
|
||||
[0,1,0].
|
||||
etc.
|
||||
```
|
||||
|
||||
## Example: Boolean circuit
|
||||
|
||||
Consider a Boolean circuit that express the Boolean function =|XOR|=
|
||||
with 4 =|NAND|= gates. We can model such a circuit with CLP(B)
|
||||
constraints as follows:
|
||||
|
||||
```
|
||||
:- use_module(library(clpb)).
|
||||
|
||||
nand_gate(X, Y, Z) :- sat(Z =:= ~(X*Y)).
|
||||
|
||||
xor(X, Y, Z) :-
|
||||
nand_gate(X, Y, T1),
|
||||
nand_gate(X, T1, T2),
|
||||
nand_gate(Y, T1, T3),
|
||||
nand_gate(T2, T3, Z).
|
||||
```
|
||||
|
||||
Using universally quantified variables, we can show that the circuit
|
||||
does compute =|XOR|= as intended:
|
||||
|
||||
```
|
||||
?- xor(x, y, Z).
|
||||
clpb:sat(Z=:=x#y).
|
||||
```
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The interface predicates of this library follow the example of
|
||||
[*SICStus Prolog*](https://sicstus.sics.se).
|
||||
|
||||
Use SICStus Prolog for higher performance in many cases.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
|
||||
variable gets an attribute (in module "clpb") of the form:
|
||||
@@ -1108,19 +1364,17 @@ indomain(1).
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ==
|
||||
% ```
|
||||
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
||||
% Vs = [A, B],
|
||||
% Count = 3,
|
||||
% sat(A=:=A*B).
|
||||
% Vs = [A,B], Count = 3, clpb:sat(A=:=A*B).
|
||||
%
|
||||
% ?- length(Vs, 120),
|
||||
% sat_count(+Vs, CountOr),
|
||||
% sat_count(*(Vs), CountAnd).
|
||||
% Vs = [...],
|
||||
% CountOr = 1329227995784915872903807060280344575,
|
||||
% CountAnd = 1.
|
||||
% ==
|
||||
% Vs = [...],
|
||||
% CountOr = 1329227995784915872903807060280344575,
|
||||
% CountAnd = 1.
|
||||
% ```
|
||||
|
||||
|
||||
|
||||
@@ -1248,7 +1502,7 @@ random_bindings(VNum, Node) -->
|
||||
% linear objective function over Boolean variables Vs with integer
|
||||
% coefficients Weights. This predicate assigns 0 and 1 to the
|
||||
% variables in Vs such that all stated constraints are satisfied, and
|
||||
% Maximum is the maximum of sum(Weight_i*V_i) over all admissible
|
||||
% Maximum is the maximum of `sum(Weight_i*V_i)` over all admissible
|
||||
% assignments. On backtracking, all admissible assignments that
|
||||
% attain the optimum are generated.
|
||||
%
|
||||
@@ -1257,10 +1511,11 @@ random_bindings(VNum, Node) -->
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ==
|
||||
% ```
|
||||
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
|
||||
% A = 0, B = 1, C = 1, Maximum = 3.
|
||||
% ==
|
||||
% A = 0, B = 1, C = 1, Maximum = 3
|
||||
% ; false.
|
||||
% ```
|
||||
|
||||
weighted_maximum(Ws, Vars, Max) :-
|
||||
must_be(list(integer), Ws),
|
||||
@@ -1373,6 +1628,7 @@ skip_to_var_(Var, Weight, [Var0-Weight0|VWs0], VWs) -->
|
||||
|
||||
attribute_goals(Var) -->
|
||||
{ var_index_root(Var, _, Root) },
|
||||
!,
|
||||
( { root_get_formula_bdd(Root, Formula, BDD) } ->
|
||||
{ del_bdd(Root) },
|
||||
( { clpb_residuals(bdd) } ->
|
||||
@@ -1400,6 +1656,10 @@ attribute_goals(Var) -->
|
||||
booleans(RestVs)
|
||||
; boolean(Var) % the variable may have occurred only in taut/2
|
||||
).
|
||||
attribute_goals(Var) -->
|
||||
{ get_atts(Var, clpb_bdd(BDD)),
|
||||
ground(BDD),
|
||||
put_atts(Var, -clpb_bdd(_)) }.
|
||||
|
||||
del_clpb(Var) :-
|
||||
del_attr(Var, clpb),
|
||||
|
||||
+566
-515
File diff suppressed because it is too large
Load Diff
+349
-347
@@ -1,20 +1,20 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2021, 2022 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
Predicates for cryptographic applications.
|
||||
/** Predicates for cryptographic applications.
|
||||
|
||||
This library assumes that the Prolog flag double_quotes is set to chars.
|
||||
This library assumes that the Prolog flag `double_quotes` is set to `chars`.
|
||||
In Scryer Prolog, lists of characters are very efficiently represented,
|
||||
and strings have the advantage that the atom table remains unmodified.
|
||||
|
||||
Especially for cryptographic applications, it is an advantage that
|
||||
using strings leaves little trace of what was processed in the system.
|
||||
|
||||
For predicates that accept an encoding/1 option to specify the encoding
|
||||
of the input data, if encoding(octet) is used, then the input can also
|
||||
be specified as a list of bytes, i.e., integers between 0 and 255.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
For predicates that accept an `encoding/1` option to specify the encoding
|
||||
of the input data, if `encoding(octet)` is used, then the input can also
|
||||
be specified as a list of _bytes_, i.e., integers between 0 and 255.
|
||||
*/
|
||||
|
||||
:- module(crypto,
|
||||
[hex_bytes/2, % ?Hex, ?Bytes
|
||||
@@ -48,20 +48,20 @@
|
||||
:- use_module(library(si)).
|
||||
:- use_module(library(iso_ext), [partial_string/3]).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
hex_bytes(?Hex, ?Bytes) is det.
|
||||
|
||||
Relation between a hexadecimal sequence and a list of bytes. Hex
|
||||
is a string of hexadecimal numbers. Bytes is a list of *integers*
|
||||
between 0 and 255 that represent the sequence as a list of bytes.
|
||||
At least one of the arguments must be instantiated.
|
||||
|
||||
Example:
|
||||
|
||||
?- hex_bytes("501ACE", Bs).
|
||||
Bs = [80,26,206].
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% hex_bytes(?Hex, ?Bytes) is det.
|
||||
%
|
||||
% Relation between a hexadecimal sequence and a list of bytes. Hex
|
||||
% is a string of hexadecimal numbers. Bytes is a list of _integers_
|
||||
% between 0 and 255 that represent the sequence as a list of bytes.
|
||||
% At least one of the arguments must be instantiated.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- hex_bytes("501ACE", Bs).
|
||||
% Bs = [80,26,206].
|
||||
% ```
|
||||
|
||||
hex_bytes(Hs, Bytes) :-
|
||||
( ground(Hs) ->
|
||||
@@ -113,47 +113,52 @@ must_be_octet_chars(Chars, Context) :-
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Cryptographically secure random numbers
|
||||
=======================================
|
||||
|
||||
crypto_n_random_bytes(+N, -Bytes) is det
|
||||
|
||||
Bytes is unified with a list of N cryptographically secure
|
||||
pseudo-random bytes. Each byte is an integer between 0 and 255. If
|
||||
the internal pseudo-random number generator (PRNG) has not been
|
||||
seeded with enough entropy to ensure an unpredictable byte
|
||||
sequence, an exception is thrown.
|
||||
|
||||
One way to relate such a list of bytes to an _integer_ is to use
|
||||
CLP(ℤ) constraints as follows:
|
||||
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
bytes_integer(Bs, N) :-
|
||||
foldl(pow, Bs, 0-0, N-_).
|
||||
|
||||
pow(B, N0-I0, N-I) :-
|
||||
B in 0..255,
|
||||
N #= N0 + B*256^I0,
|
||||
I #= I0 + 1.
|
||||
|
||||
With this definition, we can generate a random 256-bit integer
|
||||
_from_ a list of 32 random _bytes_:
|
||||
|
||||
?- crypto_n_random_bytes(32, Bs),
|
||||
bytes_integer(Bs, I).
|
||||
Bs = [146,166,162,210,242,7,25,132,64,94|...],
|
||||
I = 337420085690608915485...(56 digits omitted).
|
||||
|
||||
The above relation also works in the other direction, letting you
|
||||
translate an integer _to_ a list of bytes. In addition, you can
|
||||
use hex_bytes/2 to convert bytes to _tokens_ that can be easily
|
||||
exchanged in your applications.
|
||||
|
||||
?- crypto_n_random_bytes(12, Bs),
|
||||
hex_bytes(Hex, Bs).
|
||||
Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ...".
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% crypto_n_random_bytes(+N, -Bytes) is det.
|
||||
%
|
||||
% Bytes is unified with a list of N cryptographically secure
|
||||
% pseudo-random bytes. Each byte is an integer between 0 and 255. If
|
||||
% the internal pseudo-random number generator (PRNG) has not been
|
||||
% seeded with enough entropy to ensure an unpredictable byte
|
||||
% sequence, an exception is thrown.
|
||||
%
|
||||
% One way to relate such a list of bytes to an _integer_ is to use
|
||||
% CLP(ℤ) constraints as follows:
|
||||
%
|
||||
% ```
|
||||
% :- use_module(library(clpz)).
|
||||
% :- use_module(library(lists)).
|
||||
%
|
||||
% bytes_integer(Bs, N) :-
|
||||
% foldl(pow, Bs, 0-0, N-_).
|
||||
%
|
||||
% pow(B, N0-I0, N-I) :-
|
||||
% B in 0..255,
|
||||
% N #= N0 + B*256^I0,
|
||||
% I #= I0 + 1.
|
||||
% ```
|
||||
%
|
||||
% With this definition, we can generate a random 256-bit integer
|
||||
% _from_ a list of 32 random _bytes_:
|
||||
%
|
||||
% ```
|
||||
% ?- crypto_n_random_bytes(32, Bs),
|
||||
% bytes_integer(Bs, I).
|
||||
% Bs = [146,166,162,210,242,7,25,132,64,94|...],
|
||||
% I = 337420085690608915485...(56 digits omitted).
|
||||
% ```
|
||||
%
|
||||
% The above relation also works in the other direction, letting you
|
||||
% translate an integer _to_ a list of bytes. In addition, you can
|
||||
% use `hex_bytes/2` to convert bytes to _tokens_ that can be easily
|
||||
% exchanged in your applications.
|
||||
%
|
||||
% ```
|
||||
% ?- crypto_n_random_bytes(12, Bs),
|
||||
% hex_bytes(Hex, Bs).
|
||||
% Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ...".
|
||||
% ```
|
||||
|
||||
crypto_n_random_bytes(N, Bs) :-
|
||||
must_be(integer, N),
|
||||
@@ -165,30 +170,34 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Hashing
|
||||
=======
|
||||
|
||||
crypto_data_hash(+Data, -Hash, +Options)
|
||||
|
||||
Where Data is a list of characters, and Hash is the computed hash
|
||||
as a list of hexadecimal characters.
|
||||
|
||||
Options is a list of:
|
||||
|
||||
- algorithm(+A)
|
||||
where A is one of ripemd160, sha256, sha384, sha512, sha512_256,
|
||||
sha3_224, sha3_256, sha3_384, sha3_512, blake2s256, blake2b512,
|
||||
or a variable. If A is a variable, then it is unified with the
|
||||
default algorithm, which is an algorithm that is considered
|
||||
cryptographically secure at the time of this writing.
|
||||
- encoding(+Encoding)
|
||||
The default encoding is utf8. The alternative is octet,
|
||||
to treat the input as a list of raw bytes.
|
||||
|
||||
Example:
|
||||
|
||||
?- crypto_data_hash("abc", Hs, [algorithm(sha256)]).
|
||||
Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% crypto_data_hash(+Data, -Hash, +Options)
|
||||
%
|
||||
% Where Data is a list of characters, and Hash is the computed hash
|
||||
% as a list of hexadecimal characters.
|
||||
%
|
||||
% Options is a list of:
|
||||
%
|
||||
% - `algorithm(+A)`
|
||||
% where `A` is one of `ripemd160`, `sha256`, `sha384`, `sha512`,
|
||||
% `sha512_256`, `sha3_224`, `sha3_256`, `sha3_384`,
|
||||
% `sha3_512`, `blake2s256`, `blake2b512`, or a variable. If `A` is
|
||||
% a variable, then it is unified with the default algorithm,
|
||||
% which is an algorithm that is considered cryptographically
|
||||
% secure at the time of this writing.
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% The default encoding is `utf8`. The alternative is `octet`, to
|
||||
% treat the input as a list of raw bytes.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- crypto_data_hash("abc", Hs, [algorithm(sha256)]).
|
||||
% Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".
|
||||
% ```
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SHA256 is the current default for several hash-related predicates.
|
||||
It is deemed sufficiently secure for the foreseeable future. Yet,
|
||||
@@ -238,38 +247,36 @@ hash_algorithm(blake2s256).
|
||||
hash_algorithm(blake2b512).
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det.
|
||||
|
||||
Concentrate possibly dispersed entropy of Data and then expand it
|
||||
to the desired length. Data is a list of characters.
|
||||
|
||||
Bytes is unified with a list of bytes of length Length, and is
|
||||
suitable as input keying material and initialization vectors to
|
||||
symmetric encryption algorithms.
|
||||
|
||||
Admissible options are:
|
||||
|
||||
- algorithm(+Algorithm)
|
||||
One of sha256, sha384 or sha512. If you specify a variable,
|
||||
then it is unified with the algorithm that was used, which is a
|
||||
cryptographically secure algorithm by default.
|
||||
- info(+Info)
|
||||
Optional context and application specific information,
|
||||
specified as a list of characters. The default is [].
|
||||
- salt(+List)
|
||||
Optionally, a list of bytes that are used as salt. The
|
||||
default is all zeroes.
|
||||
- encoding(+Encoding)
|
||||
The default encoding is utf8. The alternative is octet,
|
||||
to treat the input as a list of raw bytes.
|
||||
|
||||
The `info/1` option can be used to generate multiple keys from a
|
||||
single master key, using for example values such as "key" and
|
||||
"iv", or the name of a file that is to be encrypted.
|
||||
|
||||
See crypto_n_random_bytes/2 to obtain a suitable salt.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det.
|
||||
%
|
||||
% Concentrate possibly dispersed entropy of Data and then expand it
|
||||
% to the desired length. Data is a list of characters.
|
||||
%
|
||||
% Bytes is unified with a list of bytes of length Length, and is
|
||||
% suitable as input keying material and initialization vectors to
|
||||
% symmetric encryption algorithms.
|
||||
%
|
||||
% Admissible options are:
|
||||
%
|
||||
% - `algorithm(+Algorithm)`
|
||||
% One of `sha256`, `sha384` or `sha512`. If you specify a variable,
|
||||
% then it is unified with the algorithm that was used, which is a
|
||||
% cryptographically secure algorithm by default.
|
||||
% - `info(+Info)`
|
||||
% Optional context and application specific information,
|
||||
% specified as a list of characters. The default is `[]`.
|
||||
% - `salt(+List)`
|
||||
% Optionally, a list of bytes that are used as salt. The
|
||||
% default is all zeroes.
|
||||
% - `encoding(+Encoding)`
|
||||
% The default encoding is `utf8`. The alternative is `octet`,
|
||||
% to treat the input as a list of raw bytes.
|
||||
%
|
||||
% The `info/1` option can be used to generate multiple keys from a
|
||||
% single master key, using for example values such as "key" and
|
||||
% "iv", or the name of a file that is to be encrypted.
|
||||
%
|
||||
% See `crypto_n_random_bytes/2` to obtain a suitable salt.
|
||||
|
||||
crypto_data_hkdf(Data0, L, Bytes, Options0) :-
|
||||
functor_hash_options(algorithm, Algorithm, Options0, Options),
|
||||
@@ -323,14 +330,12 @@ chars_bytes_(Cs, Bytes, Context) :-
|
||||
know if you need to rely on any specifics of this format.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
crypto_password_hash(+Password, ?Hash) is semidet.
|
||||
|
||||
If Hash is instantiated, the predicate succeeds _iff_ the hash
|
||||
matches the given password. Otherwise, the call is equivalent to
|
||||
crypto_password_hash(Password, Hash, []) and computes a
|
||||
password-based hash using the default options.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% crypto_password_hash(+Password, ?Hash) is semidet.
|
||||
%
|
||||
% If Hash is instantiated, the predicate succeeds _iff_ the hash
|
||||
% matches the given password. Otherwise, the call is equivalent to
|
||||
% `crypto_password_hash(Password, Hash, [])` and computes a
|
||||
% password-based hash using the default options.
|
||||
|
||||
crypto_password_hash(Password0, Hash) :-
|
||||
( nonvar(Hash) ->
|
||||
@@ -353,58 +358,56 @@ dollar_segments(Ls, Segments) :-
|
||||
).
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
crypto_password_hash(+Password, -Hash, +Options) is det.
|
||||
|
||||
Derive Hash based on Password. This predicate is similar to
|
||||
crypto_data_hash/3 in that it derives a hash from given data.
|
||||
However, it is tailored for the specific use case of _passwords_.
|
||||
One essential distinction is that for this use case, the derivation
|
||||
of a hash should be _as slow as possible_ to counteract brute-force
|
||||
attacks over possible passwords.
|
||||
|
||||
Another important distinction is that equal passwords must yield,
|
||||
with very high probability, _different_ hashes. For this reason,
|
||||
cryptographically strong random numbers are automatically added to
|
||||
the password before a hash is derived.
|
||||
|
||||
Hash is unified with a string that contains the computed hash and
|
||||
all parameters that were used, except for the password. Instead of
|
||||
storing passwords, store these hashes. Later, you can verify the
|
||||
validity of a password with crypto_password_hash/2, comparing the
|
||||
then entered password to the stored hash. If you need to export this
|
||||
atom, you should treat it as opaque ASCII data with up to 255 bytes
|
||||
of length. The maximal length may increase in the future.
|
||||
|
||||
Admissible options are:
|
||||
|
||||
- algorithm(+Algorithm)
|
||||
The algorithm to use. Currently, the only available algorithm
|
||||
is 'pbkdf2-sha512', which is therefore also the default.
|
||||
- cost(+C)
|
||||
C is an integer, denoting the binary logarithm of the number
|
||||
of _iterations_ used for the derivation of the hash. This
|
||||
means that the number of iterations is set to 2^C. Currently,
|
||||
the default is 17, and thus more than one hundred _thousand_
|
||||
iterations. You should set this option as high as your server
|
||||
and users can tolerate. The default is subject to change and
|
||||
will likely increase in the future or adapt to new algorithms.
|
||||
- salt(+Salt)
|
||||
Use the given list of bytes as salt. By default,
|
||||
cryptographically secure random numbers are generated for this
|
||||
purpose. The default is intended to be secure, and constitutes
|
||||
the typical use case of this predicate.
|
||||
|
||||
Currently, PBKDF2 with SHA-512 is used as the hash derivation
|
||||
function, using 128 bits of salt. All default parameters, including
|
||||
the algorithm, are subject to change, and other algorithms will also
|
||||
become available in the future. Since computed hashes store all
|
||||
parameters that were used during their derivation, such changes will
|
||||
not affect the operation of existing deployments. Note though that
|
||||
new hashes will then be computed with the new default parameters.
|
||||
|
||||
See crypto_data_hkdf/4 for generating keys from Hash.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% crypto_password_hash(+Password, -Hash, +Options) is det.
|
||||
%
|
||||
% Derive Hash based on Password. This predicate is similar to
|
||||
% `crypto_data_hash/3` in that it derives a hash from given data.
|
||||
% However, it is tailored for the specific use case of _passwords_.
|
||||
% One essential distinction is that for this use case, the derivation
|
||||
% of a hash should be _as slow as possible_ to counteract brute-force
|
||||
% attacks over possible passwords.
|
||||
%
|
||||
% Another important distinction is that equal passwords must yield,
|
||||
% with very high probability, _different_ hashes. For this reason,
|
||||
% cryptographically strong random numbers are automatically added to
|
||||
% the password before a hash is derived.
|
||||
%
|
||||
% Hash is unified with a string that contains the computed hash and
|
||||
% all parameters that were used, except for the password. Instead of
|
||||
% storing passwords, store these hashes. Later, you can verify the
|
||||
% validity of a password with `crypto_password_hash/2`, comparing the
|
||||
% then entered password to the stored hash. If you need to export this
|
||||
% atom, you should treat it as opaque ASCII data with up to 255 bytes
|
||||
% of length. The maximal length may increase in the future.
|
||||
%
|
||||
% Admissible options are:
|
||||
%
|
||||
% - `algorithm(+Algorithm)`
|
||||
% The algorithm to use. Currently, the only available algorithm
|
||||
% is `'pbkdf2-sha512'`, which is therefore also the default.
|
||||
% - `cost(+C)`
|
||||
% C is an integer, denoting the binary logarithm of the number
|
||||
% of _iterations_ used for the derivation of the hash. This
|
||||
% means that the number of iterations is set to 2^C. Currently,
|
||||
% the default is 17, and thus more than one hundred _thousand_
|
||||
% iterations. You should set this option as high as your server
|
||||
% and users can tolerate. The default is subject to change and
|
||||
% will likely increase in the future or adapt to new algorithms.
|
||||
% - `salt(+Salt)`
|
||||
% Use the given list of bytes as salt. By default,
|
||||
% cryptographically secure random numbers are generated for this
|
||||
% purpose. The default is intended to be secure, and constitutes
|
||||
% the typical use case of this predicate.
|
||||
%
|
||||
% Currently, PBKDF2 with SHA-512 is used as the hash derivation
|
||||
% function, using 128 bits of salt. All default parameters, including
|
||||
% the algorithm, are subject to change, and other algorithms will also
|
||||
% become available in the future. Since computed hashes store all
|
||||
% parameters that were used during their derivation, such changes will
|
||||
% not affect the operation of existing deployments. Note though that
|
||||
% new hashes will then be computed with the new default parameters.
|
||||
%
|
||||
% See `crypto_data_hkdf/4` for generating keys from Hash.
|
||||
|
||||
crypto_password_hash(Password0, Hash, Options) :-
|
||||
chars_bytes_(Password0, Password, crypto_password_hash/3),
|
||||
@@ -435,97 +438,94 @@ bytes_base64(Bytes, Base64) :-
|
||||
chars_base64(Chars, Base64, [padding(false)])
|
||||
).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
crypto_data_encrypt(+PlainText,
|
||||
+Algorithm,
|
||||
+Key,
|
||||
+IV,
|
||||
-CipherText,
|
||||
+Options).
|
||||
|
||||
Encrypt the given PlainText, using the symmetric algorithm
|
||||
Algorithm, key Key, and initialization vector (or nonce) IV, to
|
||||
give CipherText.
|
||||
|
||||
PlainText must be a list of characters, Key and IV must be lists of
|
||||
bytes, and CipherText is created as a list of characters.
|
||||
|
||||
Keys and IVs can be chosen at random (using for example
|
||||
crypto_n_random_bytes/2) or derived from input keying material (IKM)
|
||||
using for example crypto_data_hkdf/4. This input is often a shared
|
||||
secret, such as a negotiated point on an elliptic curve, or the hash
|
||||
that was computed from a password via crypto_password_hash/3 with a
|
||||
freshly generated and specified _salt_.
|
||||
|
||||
Reusing the same combination of Key and IV typically leaks at least
|
||||
_some_ information about the plaintext. For example, identical
|
||||
plaintexts will then correspond to identical ciphertexts. For some
|
||||
algorithms, reusing an IV with the same Key has disastrous results
|
||||
and can cause the loss of all properties that are otherwise
|
||||
guaranteed. Especially in such cases, an IV is also called a
|
||||
_nonce_ (number used once).
|
||||
|
||||
It is safe to store and transfer the used initialization vector (or
|
||||
nonce) in plain text, but the key _must be kept secret_.
|
||||
|
||||
Currently, the only supported algorithm is 'chacha20-poly1305', a
|
||||
powerful and efficient _authenticated_ encryption scheme, providing
|
||||
secrecy and at the same time reliable protection against undetected
|
||||
_modifications_ of the encrypted data. This is a very good choice
|
||||
for virtually all use cases. It is a stream cipher and can encrypt
|
||||
data of any length up to 256 GB. Further, the encrypted data has
|
||||
exactly the same length as the original, and no padding is used.
|
||||
|
||||
Options:
|
||||
|
||||
- encoding(+Encoding)
|
||||
Encoding to use for PlainText. Default is utf8. The alternative
|
||||
is octet to treat PlainText as raw bytes.
|
||||
|
||||
- tag(-List)
|
||||
For authenticated encryption schemes, List is unified with a
|
||||
list of _bytes_ holding the tag. This tag must be provided for
|
||||
decryption.
|
||||
|
||||
- aad(+Data)
|
||||
Data is additional authenticated data (AAD), a list of
|
||||
characters. It is authenticated in that it influences the tag,
|
||||
but it is not encrypted. The encoding/1 option also specifies
|
||||
the encoding of Data.
|
||||
|
||||
Here is an example encryption and decryption, using the ChaCha20
|
||||
stream cipher with the Poly1305 authenticator. This cipher uses a
|
||||
256-bit key and a 96-bit nonce, i.e., 32 and 12 _bytes_,
|
||||
respectively:
|
||||
|
||||
?- Algorithm = 'chacha20-poly1305',
|
||||
crypto_n_random_bytes(32, Key),
|
||||
crypto_n_random_bytes(12, IV),
|
||||
crypto_data_encrypt("this text is to be encrypted", Algorithm,
|
||||
Key, IV, CipherText, [tag(Tag)]),
|
||||
crypto_data_decrypt(CipherText, Algorithm,
|
||||
Key, IV, RecoveredText, [tag(Tag)]).
|
||||
|
||||
Yielding:
|
||||
|
||||
Algorithm = 'chacha20-poly1305',
|
||||
Key = [113,247,153,134,177,220,13,193,50,150|...],
|
||||
IV = [135,20,149,153,63,35,68,114,247,171|...],
|
||||
CipherText = "\x94\0Ej\x94\®Â\x95\óÑÆXÃn¾ð©b\x1c\ ...",
|
||||
RecoveredText = "this text is to be ...",
|
||||
Tag = [152,117,152,17,162,75,150,206,144,40|...]
|
||||
|
||||
In this example, we use crypto_n_random_bytes/2 to generate a key
|
||||
and nonce from cryptographically secure random numbers. For
|
||||
repeated applications, you must ensure that a nonce is only used
|
||||
_once_ together with the same key. Note that for _authenticated_
|
||||
encryption schemes, the _tag_ that was computed during encryption
|
||||
is necessary for decryption. It is safe to store and transfer the
|
||||
tag in plain text.
|
||||
|
||||
See also crypto_data_decrypt/6, and hex_bytes/2 for conversion
|
||||
between bytes and hex encoding.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% crypto_data_encrypt(+PlainText, +Algorithm, +Key, +IV, -CipherText, +Options).
|
||||
%
|
||||
% Encrypt the given PlainText, using the symmetric algorithm
|
||||
% Algorithm, key Key, and initialization vector (or nonce) IV, to
|
||||
% give CipherText.
|
||||
%
|
||||
% PlainText must be a list of characters, Key and IV must be lists of
|
||||
% bytes, and CipherText is created as a list of characters.
|
||||
%
|
||||
% Keys and IVs can be chosen at random (using for example
|
||||
% `crypto_n_random_bytes/2`) or derived from input keying material (IKM)
|
||||
% using for example `crypto_data_hkdf/4`. This input is often a shared
|
||||
% secret, such as a negotiated point on an elliptic curve, or the hash
|
||||
% that was computed from a password via `crypto_password_hash/3` with a
|
||||
% freshly generated and specified _salt_.
|
||||
%
|
||||
% Reusing the same combination of Key and IV typically leaks at least
|
||||
% _some_ information about the plaintext. For example, identical
|
||||
% plaintexts will then correspond to identical ciphertexts. For some
|
||||
% algorithms, reusing an IV with the same Key has disastrous results
|
||||
% and can cause the loss of all properties that are otherwise
|
||||
% guaranteed. Especially in such cases, an IV is also called a
|
||||
% _nonce_ (number used once).
|
||||
%
|
||||
% It is safe to store and transfer the used initialization vector (or
|
||||
% nonce) in plain text, but the key _must be kept secret_.
|
||||
%
|
||||
% Currently, the only supported algorithm is 'chacha20-poly1305', a
|
||||
% powerful and efficient _authenticated_ encryption scheme, providing
|
||||
% secrecy and at the same time reliable protection against undetected
|
||||
% _modifications_ of the encrypted data. This is a very good choice
|
||||
% for virtually all use cases. It is a stream cipher and can encrypt
|
||||
% data of any length up to 256 GB. Further, the encrypted data has
|
||||
% exactly the same length as the original, and no padding is used.
|
||||
%
|
||||
% Options:
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% Encoding to use for PlainText. Default is utf8. The alternative
|
||||
% is octet to treat PlainText as raw bytes.
|
||||
%
|
||||
% - `tag(-List)`
|
||||
% For authenticated encryption schemes, List is unified with a
|
||||
% list of _bytes_ holding the tag. This tag must be provided for
|
||||
% decryption.
|
||||
%
|
||||
% - `aad(+Data)`
|
||||
% Data is additional authenticated data (AAD), a list of
|
||||
% characters. It is authenticated in that it influences the tag,
|
||||
% but it is not encrypted. The `encoding/1` option also specifies
|
||||
% the encoding of Data.
|
||||
%
|
||||
% Here is an example encryption and decryption, using the ChaCha20
|
||||
% stream cipher with the Poly1305 authenticator. This cipher uses a
|
||||
% 256-bit key and a 96-bit nonce, i.e., 32 and 12 _bytes_,
|
||||
% respectively:
|
||||
%
|
||||
% ```
|
||||
% ?- Algorithm = 'chacha20-poly1305',
|
||||
% crypto_n_random_bytes(32, Key),
|
||||
% crypto_n_random_bytes(12, IV),
|
||||
% crypto_data_encrypt("this text is to be encrypted", Algorithm,
|
||||
% Key, IV, CipherText, [tag(Tag)]),
|
||||
% crypto_data_decrypt(CipherText, Algorithm,
|
||||
% Key, IV, RecoveredText, [tag(Tag)]).
|
||||
% ```
|
||||
%
|
||||
% Yielding:
|
||||
%
|
||||
% ```
|
||||
% Algorithm = 'chacha20-poly1305',
|
||||
% Key = [113,247,153,134,177,220,13,193,50,150|...],
|
||||
% IV = [135,20,149,153,63,35,68,114,247,171|...],
|
||||
% CipherText = "\x94\0Ej\x94\®Â\x95\óÑÆXÃn¾ð©b\x1c\ ...",
|
||||
% RecoveredText = "this text is to be ...",
|
||||
% Tag = [152,117,152,17,162,75,150,206,144,40|...]
|
||||
% ```
|
||||
%
|
||||
% In this example, we use `crypto_n_random_bytes/2` to generate a key
|
||||
% and nonce from cryptographically secure random numbers. For
|
||||
% repeated applications, you must ensure that a nonce is only used
|
||||
% _once_ together with the same key. Note that for _authenticated_
|
||||
% encryption schemes, the _tag_ that was computed during encryption
|
||||
% is necessary for decryption. It is safe to store and transfer the
|
||||
% tag in plain text.
|
||||
%
|
||||
% See also `crypto_data_decrypt/6`, and `hex_bytes/2` for conversion
|
||||
% between bytes and hex encoding.
|
||||
|
||||
crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :-
|
||||
options_data_chars(Options, PlainText0, PlainText, Encoding),
|
||||
@@ -549,37 +549,30 @@ algorithm_key_iv('chacha20-poly1305', Key, IV) :-
|
||||
length(Key, 32),
|
||||
length(IV, 12).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
crypto_data_decrypt(+CipherText,
|
||||
+Algorithm,
|
||||
+Key,
|
||||
+IV,
|
||||
-PlainText,
|
||||
+Options).
|
||||
|
||||
Decrypt the given CipherText, using the symmetric algorithm
|
||||
Algorithm, key Key, and initialization vector IV, to give
|
||||
PlainText. CipherText must be a list of characters, and Key and IV
|
||||
must be lists of bytes. PlainText is created as a list of
|
||||
characters.
|
||||
|
||||
Currently, the only supported algorithm is 'chacha20-poly1305',
|
||||
a very secure, fast and versatile authenticated encryption method.
|
||||
|
||||
Options is a list of:
|
||||
|
||||
- encoding(+Encoding)
|
||||
Encoding to use for PlainText. The default is utf8. The
|
||||
alternative is octet, which is used if the data are raw bytes.
|
||||
|
||||
- tag(+Tag)
|
||||
For authenticated encryption schemes, the tag must be specified as
|
||||
a list of bytes exactly as they were generated upon encryption.
|
||||
|
||||
- aad(+Data)
|
||||
Any additional authenticated data (AAD) must be specified. The
|
||||
encoding/1 option also specifies the encoding of Data.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% crypto_data_decrypt(+CipherText, +Algorithm, +Key, +IV, -PlainText, +Options).
|
||||
%
|
||||
% Decrypt the given CipherText, using the symmetric algorithm
|
||||
% Algorithm, key Key, and initialization vector IV, to give
|
||||
% PlainText. CipherText must be a list of characters, and Key and IV
|
||||
% must be lists of bytes. PlainText is created as a list of
|
||||
% characters.
|
||||
%
|
||||
% Currently, the only supported algorithm is 'chacha20-poly1305',
|
||||
% a very secure, fast and versatile authenticated encryption method.
|
||||
%
|
||||
% Options is a list of:
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% Encoding to use for PlainText. The default is utf8. The
|
||||
% alternative is octet, which is used if the data are raw bytes.
|
||||
%
|
||||
% - `tag(+Tag)`
|
||||
% For authenticated encryption schemes, the tag must be specified as
|
||||
% a list of bytes exactly as they were generated upon encryption.
|
||||
%
|
||||
% - `aad(+Data)`
|
||||
% Any additional authenticated data (AAD) must be specified. The
|
||||
% `encoding/1` option also specifies the encoding of Data.
|
||||
|
||||
crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :-
|
||||
option(tag(Tag), Options, []),
|
||||
@@ -617,49 +610,53 @@ encoding_chars(utf8, Cs, Cs) :-
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Digital signatures with Ed25519
|
||||
===============================
|
||||
|
||||
- ed25519_new_keypair(-Pair)
|
||||
Yields a new Ed25519 key pair Pair, a list of characters. The
|
||||
pair contains the private key and must be kept absolutely secret.
|
||||
Pair can be used for signing. Its public key can be obtained
|
||||
with ed25519_keypair_public_key/2.
|
||||
|
||||
- ed25519_keypair_public_key(+Pair, -PublicKey)
|
||||
PublicKey is the public key of the given key pair. The public key
|
||||
can be used for signature verification, and can be shared freely.
|
||||
The public key is represented as a list of characters.
|
||||
|
||||
- ed25519_sign(+Key, +Data, -Signature, +Options)
|
||||
Key and Data must be lists of characters. Key is a key pair in
|
||||
PKCS#8 v2 format as generated by ed25519_new_keypair/1. Sign Data
|
||||
with Key, yielding Signature as a list of hexadecimal characters.
|
||||
|
||||
- ed25519_verify(+Key, +Data, +Signature, +Options)
|
||||
Key and Data must be lists of characters. Key is a public key.
|
||||
Succeeds if Data was signed with the private key corresponding to
|
||||
Key, where Signature is a list of hexadecimal characters as
|
||||
generated by ed25519_sign/4. Fails otherwise.
|
||||
|
||||
Currently, the only option for signing and verifying is:
|
||||
|
||||
- encoding(+Encoding)
|
||||
The default encoding of Data is utf8. The alternative is octet,
|
||||
which treats Data as a list of raw bytes.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% ed25519_new_keypair(-Pair)
|
||||
%
|
||||
% Yields a new Ed25519 key pair Pair, a list of characters. The
|
||||
% pair contains the private key and must be kept absolutely secret.
|
||||
% Pair can be used for signing. Its public key can be obtained
|
||||
% with `ed25519_keypair_public_key/2`.
|
||||
|
||||
ed25519_new_keypair(Pair) :-
|
||||
'$ed25519_new_keypair'(Pair).
|
||||
|
||||
%% ed25519_keypair_public_key(+Pair, -PublicKey)
|
||||
%
|
||||
% PublicKey is the public key of the given key pair. The public key
|
||||
% can be used for signature verification, and can be shared freely.
|
||||
% The public key is represented as a list of characters.
|
||||
|
||||
ed25519_keypair_public_key(Pair, PublicKey) :-
|
||||
must_be_octet_chars(Pair, ed25519_keypair_public_key),
|
||||
'$ed25519_keypair_public_key'(Pair, PublicKey).
|
||||
|
||||
%% ed25519_sign(+Key, +Data, -Signature, +Options)
|
||||
%
|
||||
% Key and Data must be lists of characters. Key is a key pair in
|
||||
% PKCS#8 v2 format as generated by `ed25519_new_keypair/1`. Sign Data
|
||||
% with Key, yielding Signature as a list of hexadecimal characters.
|
||||
|
||||
ed25519_sign(Key, Data0, Signature, Options) :-
|
||||
must_be_octet_chars(Key, ed25519_sign),
|
||||
options_data_chars(Options, Data0, Data, Encoding),
|
||||
'$ed25519_sign'(Key, Data, Encoding, Signature0),
|
||||
hex_bytes(Signature, Signature0).
|
||||
|
||||
%% ed25519_verify(+Key, +Data, +Signature, +Options)
|
||||
%
|
||||
% Key and Data must be lists of characters. Key is a public key.
|
||||
% Succeeds if Data was signed with the private key corresponding to
|
||||
% Key, where Signature is a list of hexadecimal characters as
|
||||
% generated by `ed25519_sign/4`. Fails otherwise.
|
||||
%
|
||||
% Currently, the only option for signing and verifying is:
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% The default encoding of Data is `utf8`. The alternative is `octet`,
|
||||
% which treats Data as a list of raw bytes.
|
||||
|
||||
ed25519_verify(Key, Data0, Signature0, Options) :-
|
||||
must_be_octet_chars(Key, ed25519_verify),
|
||||
options_data_chars(Options, Data0, Data, Encoding),
|
||||
@@ -669,38 +666,43 @@ ed25519_verify(Key, Data0, Signature0, Options) :-
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
X25519: ECDH key exchange over Curve25519
|
||||
=========================================
|
||||
|
||||
Points on Curve25519 are represented as lists of characters that denote
|
||||
the u-coordinate of the Montgomery curve.
|
||||
|
||||
- curve25519_generator(-Gs)
|
||||
Gs is the generator point of Curve25519.
|
||||
|
||||
- curve25519_scalar_mult(+Scalar, +Ps, -Rs)
|
||||
Scalar must be an integer between 0 and 2^256-1,
|
||||
or a list of 32 bytes, and Ps must be a point on the curve.
|
||||
Computes the point Rs = Scalar*Ps as mandated by X25519.
|
||||
|
||||
Alice and Bob can use this to establish a shared secret as follows,
|
||||
where Gs is the generator point of Curve25519:
|
||||
|
||||
1. Alice creates a random integer a and sends As = a*Gs to Bob.
|
||||
2. Bob creates a random integer b and sends Bs = b*Gs to Alice.
|
||||
3. Alice computes Rs = a*Bs.
|
||||
4. Bob computes Rs = b*As.
|
||||
5. Alice and Bob use crypto_data_hkdf/4 on Rs with suitable
|
||||
(same) parameters to obtain lists of bytes that can be used as
|
||||
keys and initialization vectors for symmetric encryption.
|
||||
|
||||
If a and b are kept secret, this method is considered very secure.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% curve25519_generator(-Gs)
|
||||
%
|
||||
% Points on Curve25519 are represented as lists of characters that
|
||||
% denote the u-coordinate of the Montgomery curve. Gs is the
|
||||
% generator point of Curve25519.
|
||||
|
||||
curve25519_generator(Gs) :-
|
||||
length(Gs0, 32),
|
||||
Gs0 = [9|Zs],
|
||||
maplist(=(0), Zs),
|
||||
maplist(char_code, Gs, Gs0).
|
||||
|
||||
%% curve25519_scalar_mult(+Scalar, +Ps, -Rs)
|
||||
%
|
||||
% Scalar must be an integer between 0 and 2^256-1,
|
||||
% or a list of 32 bytes, and Ps must be a point on the curve.
|
||||
% Computes the point _Rs = Scalar*Ps as_ mandated by X25519.
|
||||
%
|
||||
% Alice and Bob can use this to establish a shared secret as follows,
|
||||
% where Gs is the generator point of Curve25519:
|
||||
%
|
||||
% 1. Alice creates a random integer _a_ and sends _As = a*Gs_ to Bob.
|
||||
%
|
||||
% 2. Bob creates a random integer _b_ and sends _Bs = b*Gs_ to Alice.
|
||||
%
|
||||
% 3. Alice computes _Rs = a*Bs_.
|
||||
%
|
||||
% 4. Bob computes _Rs = b*As_.
|
||||
%
|
||||
% 5. Alice and Bob use `crypto_data_hkdf/4` on Rs with suitable
|
||||
% (same) parameters to obtain lists of bytes that can be used as
|
||||
% keys and initialization vectors for symmetric encryption.
|
||||
%
|
||||
% If _a_ and _b_ are kept secret, this method is considered very secure.
|
||||
|
||||
curve25519_scalar_mult(Scalar, Point, Result) :-
|
||||
( integer_si(Scalar) ->
|
||||
length(ScalarBytes, 32),
|
||||
|
||||
+46
-33
@@ -1,54 +1,67 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Predicates for parsing CSV data
|
||||
/** Predicates for parsing CSV data
|
||||
|
||||
## Read CSV files.
|
||||
|
||||
Read csv files
|
||||
Only two options with default values:
|
||||
|
||||
Only two options with default values :
|
||||
- token_separator(',')
|
||||
- with_header(true)
|
||||
- `token_separator(',')`
|
||||
- `with_header(true)`
|
||||
|
||||
Examples
|
||||
### Examples:
|
||||
|
||||
* parsing a csv string:
|
||||
Parsing a CSV string:
|
||||
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(dcgs)).
|
||||
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
|
||||
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(dcgs)).
|
||||
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
|
||||
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
|
||||
```
|
||||
|
||||
* with some options:
|
||||
With some options:
|
||||
|
||||
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
|
||||
Data = frame([],[["one",2,[],"three"]]).
|
||||
```
|
||||
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
|
||||
Data = frame([],[["one",2,[],"three"]]).
|
||||
```
|
||||
|
||||
* parsing a csv file:
|
||||
Parsing a CSV file:
|
||||
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(pio)).
|
||||
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- use_module(library(pio)).
|
||||
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
|
||||
```
|
||||
|
||||
## Write CSV files
|
||||
|
||||
Write csv files
|
||||
Four options with default values :
|
||||
|
||||
Four options with default values :
|
||||
- line_separator('\n')
|
||||
- token_separator(',')
|
||||
- with_header(true)
|
||||
- null_value(empty)
|
||||
- `line_separator('\n')`
|
||||
- `token_separator(',')`
|
||||
- `with_header(true)`
|
||||
- `null_value(empty)`
|
||||
|
||||
Examples
|
||||
### Examples
|
||||
|
||||
* writing a csv file:
|
||||
Writing a CSV file:
|
||||
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
|
||||
```
|
||||
|
||||
* with some options
|
||||
With some options
|
||||
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]]), [with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
```
|
||||
?- use_module(library(csv)).
|
||||
?- write_csv('./test.csv', frame(
|
||||
["col1","col2","col3","col4"],
|
||||
[["one",2,[],"three"]]
|
||||
),
|
||||
[with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
|
||||
```
|
||||
*/
|
||||
|
||||
:- module(csv, [
|
||||
parse_csv//1,
|
||||
|
||||
+60
-9
@@ -1,3 +1,13 @@
|
||||
/** Support for Definite Clause Grammars.
|
||||
|
||||
A Prolog definite clause grammar (DCG) describes a sequence. Operationally, DCGs
|
||||
can be used to parse, generate, complete and check sequences manifested as lists.
|
||||
|
||||
Check [The Power of Prolog chapter on DCGs](https://www.metalevel.at/prolog/dcg)
|
||||
to learn more about them.
|
||||
*/
|
||||
|
||||
|
||||
:- module(dcgs,
|
||||
[op(1105, xfy, '|'),
|
||||
phrase/2,
|
||||
@@ -16,9 +26,44 @@
|
||||
|
||||
:- meta_predicate phrase(2, ?, ?).
|
||||
|
||||
%% phrase(+Body, ?Ls).
|
||||
%
|
||||
% True iff Body describes the list Ls. Body must be a DCG body.
|
||||
% It is equivalent to `phrase(Body, Ls, [])`.
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% as --> [].
|
||||
% as --> [a], as.
|
||||
%
|
||||
% ?- phrase(as, Ls).
|
||||
% Ls = []
|
||||
% ; Ls = "a"
|
||||
% ; Ls = "aa"
|
||||
% ; Ls = "aaa"
|
||||
% ; ... .
|
||||
%
|
||||
% ?- phrase(as, "aaa").
|
||||
% true.
|
||||
% ```
|
||||
|
||||
phrase(GRBody, S0) :-
|
||||
phrase(GRBody, S0, []).
|
||||
|
||||
%% phrase(+Body, ?Ls, ?Ls0).
|
||||
%
|
||||
% True iff Body describes part of the list Ls and the rest of Ls is Ls0.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- phrase(seq(X), "aaa", Y).
|
||||
% X = [], Y = "aaa"
|
||||
% ; X = "a", Y = "aa"
|
||||
% ; X = "aa", Y = "a"
|
||||
% ; X = "aaa", Y = [].
|
||||
% ```
|
||||
phrase(GRBody, S0, S) :-
|
||||
strip_module(GRBody, M, GRBody1),
|
||||
( var(GRBody) ->
|
||||
@@ -30,13 +75,6 @@ phrase(GRBody, S0, S) :-
|
||||
; call(M:GRBody1, S0, S)
|
||||
).
|
||||
|
||||
|
||||
module_call_qualified(M, Call, Call1) :-
|
||||
( nonvar(M) -> Call1 = M:Call
|
||||
; Call = Call1
|
||||
).
|
||||
|
||||
|
||||
% The same version of the below two dcg_rule clauses, but with module scoping.
|
||||
dcg_rule(( M:NonTerminal, Terminals --> GRBody ), ( M:Head :- Body )) :-
|
||||
dcg_non_terminal(NonTerminal, S0, S, Head),
|
||||
@@ -82,7 +120,10 @@ dcg_body(NonTerminal, S0, S, Goal1) :-
|
||||
NonTerminal \= ( \+ _ ),
|
||||
loader:strip_module(NonTerminal, M, NonTerminal0),
|
||||
dcg_non_terminal(NonTerminal0, S0, S, Goal0),
|
||||
module_call_qualified(M, Goal0, Goal1).
|
||||
( functor(NonTerminal, (:), 2) ->
|
||||
Goal1 = M:Goal0
|
||||
; Goal1 = Goal0
|
||||
).
|
||||
|
||||
% The following constructs in a grammar rule body
|
||||
% are defined in the corresponding subclauses.
|
||||
@@ -131,6 +172,9 @@ user:term_expansion(Term0, Term) :-
|
||||
nonvar(Term0),
|
||||
dcg_rule(Term0, Term).
|
||||
|
||||
|
||||
%% seq(Seq)//
|
||||
%
|
||||
% Describes a sequence
|
||||
seq(Xs, Cs0,Cs) :-
|
||||
var(Xs),
|
||||
@@ -141,10 +185,14 @@ seq(Xs, Cs0,Cs) :-
|
||||
seq([]) --> [].
|
||||
seq([E|Es]) --> [E], seq(Es).
|
||||
|
||||
%% seqq(SeqOfSeqs)//
|
||||
%
|
||||
% Describes a sequence of sequences
|
||||
seqq([]) --> [].
|
||||
seqq([Es|Ess]) --> seq(Es), seqq(Ess).
|
||||
|
||||
%% ...//
|
||||
%
|
||||
% Describes an arbitrary number of elements
|
||||
...(Cs0,Cs) :-
|
||||
Cs0 == [],
|
||||
@@ -163,6 +211,9 @@ user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
|
||||
E,
|
||||
dcgs:error_goal(E, GRBody1)
|
||||
),
|
||||
module_call_qualified(M, GRBody1, GRBody2).
|
||||
( GRBody = (_:_) ->
|
||||
GRBody2 = M:GRBody1
|
||||
; GRBody2 = GRBody1
|
||||
).
|
||||
|
||||
user:goal_expansion(phrase(GRBody, S), phrase(GRBody, S, [])).
|
||||
|
||||
+32
-1
@@ -1,4 +1,22 @@
|
||||
% Source: https://stackoverflow.com/a/30791637
|
||||
/** Declarative debugging.
|
||||
|
||||
This library provides three predicates with associated operators.
|
||||
The operators can be placed in front of goals to debug Prolog
|
||||
programs.
|
||||
|
||||
Of these predicates, the most frequently used is `(*)/1`, with
|
||||
associated prefix operator `*` (star). Placing `*` in front of a
|
||||
goal means to _generalize away_ the goal. `* Goal` acts as if `Goal`
|
||||
did not appear at all in the source code. It is declaratively
|
||||
equivalent to _commenting out_ the goal, and easier to write,
|
||||
because `*` can also be placed in front of the last goal in a clause
|
||||
without any additional changes.
|
||||
|
||||
Source: [https://stackoverflow.com/a/30791637](https://stackoverflow.com/a/30791637)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
:- module(debug, [
|
||||
op(900, fx, $),
|
||||
@@ -15,12 +33,25 @@
|
||||
:- meta_predicate $(0).
|
||||
:- meta_predicate $-(0).
|
||||
|
||||
%% $-(Goal)
|
||||
%
|
||||
% Portray exceptions thrown by Goal.
|
||||
|
||||
$-(G_0) :-
|
||||
catch(G_0, Ex, ( portray_clause(exception:Ex:G_0), throw(Ex) ) ).
|
||||
|
||||
%% $(Goal)
|
||||
%
|
||||
% Provide a _trace_ for calls of Goal.
|
||||
|
||||
$(G_0) :-
|
||||
portray_clause(call:G_0),
|
||||
$-G_0,
|
||||
portray_clause(exit:G_0).
|
||||
|
||||
%% *(Goal)
|
||||
%
|
||||
% Generalize away Goal.
|
||||
|
||||
|
||||
*(_).
|
||||
|
||||
+164
-1
@@ -1,7 +1,160 @@
|
||||
:- module(diag, [wam_instructions/2]).
|
||||
:- module(diag, [wam_instructions/2, inlined_instructions/2]).
|
||||
|
||||
/** Diagnostics library
|
||||
|
||||
The predicate `wam_instructions/2` _decompiles_ a predicate so that
|
||||
we can inspect its Warren Abstract Machine (WAM) instructions.
|
||||
In this way, we can verify and reason about compiled programs,
|
||||
and detect opportunities for optimization.
|
||||
|
||||
For example, we have:
|
||||
|
||||
```
|
||||
?- use_module(library(lists)).
|
||||
true.
|
||||
?- use_module(library(diag)).
|
||||
true.
|
||||
?- use_module(library(format)).
|
||||
true.
|
||||
?- wam_instructions(append/3, Is),
|
||||
maplist(portray_clause, Is).
|
||||
switch_on_term(1,external(1),external(2),external(6),fail).
|
||||
try_me_else(4).
|
||||
get_constant(level(shallow),[],x(1)).
|
||||
get_value(x(2),3).
|
||||
proceed.
|
||||
trust_me(0).
|
||||
get_list(level(shallow),x(1)).
|
||||
unify_variable(x(4)).
|
||||
unify_variable(x(1)).
|
||||
get_list(level(shallow),x(3)).
|
||||
unify_value(x(4)).
|
||||
unify_variable(x(3)).
|
||||
execute(append,3).
|
||||
Is = [switch_on_term(1,external(1),external(2),external(6),fail)|...].
|
||||
```
|
||||
|
||||
`inlined_instructions/2` decompiles predicates at the code offset in
|
||||
its first argument.
|
||||
|
||||
For example, given the program
|
||||
|
||||
```
|
||||
?- [user].
|
||||
:- use_module(library(clpz)).
|
||||
|
||||
all_eq(Vs, E) :- maplist(#=(E), Vs).
|
||||
|
||||
```
|
||||
|
||||
we inspect the code of `all_eqs/2` using `wam_instructions/2`,
|
||||
revealing:
|
||||
|
||||
```
|
||||
?- wam_instructions(all_eq/2, Is),
|
||||
maplist(portray_clause, Is).
|
||||
put_structure('$aux',2,x(3)).
|
||||
set_local_value(x(2)).
|
||||
set_void(1).
|
||||
set_constant('$index_ptr'(115334)).
|
||||
get_variable(x(4),1).
|
||||
put_structure(:,2,x(1)).
|
||||
set_constant(user).
|
||||
set_local_value(x(3)).
|
||||
get_variable(x(5),2).
|
||||
put_value(x(4),2).
|
||||
execute(maplist,2).
|
||||
Is = [put_structure('$aux',2,x(3)),set_local_value(x(2)),set_void(1),set_constant('$index_ptr'(115334)),get_variable(x(4),1),put_structure(:,2,x(1)),set_constant(user),set_local_value(x(3)),get_variable(x(5),2),put_value(x(4),2),execute(maplist,2)].
|
||||
```
|
||||
|
||||
The `'$index_ptr(115334)` functor gives a code offset to an inlined
|
||||
predicate compiled for the use of maplist/2. `inlined_instructions/2`
|
||||
can be used to decompile its source code:
|
||||
|
||||
```
|
||||
?- inlined_instructions(115334, Is),
|
||||
maplist(portray_clause, Is).
|
||||
allocate(1).
|
||||
get_level(y(1)).
|
||||
get_variable(x(5),2).
|
||||
put_value(x(3),2).
|
||||
get_variable(x(6),3).
|
||||
put_value(x(5),3).
|
||||
put_unsafe_value(1,4).
|
||||
deallocate.
|
||||
jmp_by_execute(1).
|
||||
try_me_else(8).
|
||||
call(integer,1).
|
||||
neck_cut.
|
||||
get_variable(x(5),1).
|
||||
put_value(x(2),1).
|
||||
get_variable(x(6),2).
|
||||
put_value(x(5),2).
|
||||
jmp_by_execute(7).
|
||||
try_me_else(12).
|
||||
allocate(3).
|
||||
get_level(y(1)).
|
||||
get_variable(y(3),1).
|
||||
get_variable(y(2),2).
|
||||
call_default(true,0).
|
||||
call(var,1).
|
||||
cut(y(1)).
|
||||
put_unsafe_value(3,1).
|
||||
put_unsafe_value(2,2).
|
||||
deallocate.
|
||||
execute_default(is,2).
|
||||
default_retry_me_else(4).
|
||||
call(integer,1).
|
||||
neck_cut.
|
||||
execute(=:=,2).
|
||||
default_trust_me(0).
|
||||
allocate(2).
|
||||
get_variable(y(1),1).
|
||||
get_variable(y(2),3).
|
||||
put_value(y(2),1).
|
||||
call_default(is,2).
|
||||
put_unsafe_value(2,1).
|
||||
put_unsafe_value(1,2).
|
||||
deallocate.
|
||||
execute_default(clpz_equal,2).
|
||||
default_retry_me_else(4).
|
||||
call(integer,1).
|
||||
neck_cut.
|
||||
jmp_by_execute(29).
|
||||
try_me_else(12).
|
||||
allocate(3).
|
||||
get_level(y(1)).
|
||||
get_variable(y(3),1).
|
||||
get_variable(y(2),2).
|
||||
call_default(true,0).
|
||||
call(var,1).
|
||||
cut(y(1)).
|
||||
put_unsafe_value(3,1).
|
||||
put_unsafe_value(2,2).
|
||||
deallocate.
|
||||
execute_default(is,2).
|
||||
default_trust_me(0).
|
||||
allocate(2).
|
||||
get_variable(y(2),1).
|
||||
get_variable(y(1),3).
|
||||
put_value(y(1),1).
|
||||
call_default(is,2).
|
||||
put_unsafe_value(2,1).
|
||||
put_unsafe_value(1,2).
|
||||
deallocate.
|
||||
execute_default(clpz_equal,2).
|
||||
default_trust_me(0).
|
||||
execute_default(clpz_equal,2).
|
||||
Is = [allocate(1),get_level(y(1)),get_variable(x(5),2),put_value(x(3),2),get_variable(x(6),3),put_value(x(5),3),put_unsafe_value(1,4),deallocate,jmp_by_execute(1),try_me_else(8),call(integer,1),neck_cut,get_variable(x(5),1),put_value(x(2),1),get_variable(x(6),2),put_value(x(5),2),jmp_by_execute(7),try_me_else(12),allocate(3),get_level(...),...].
|
||||
```
|
||||
*/
|
||||
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% wam_instructions(+PI, -Instrs)
|
||||
%
|
||||
% _Instrs_ are the WAM instructions corresponding to predicate indicator _PI_.
|
||||
|
||||
wam_instructions(Clause, Listing) :-
|
||||
( nonvar(Clause) ->
|
||||
@@ -13,6 +166,16 @@ wam_instructions(Clause, Listing) :-
|
||||
; throw(error(instantiation_error, wam_instructions/2))
|
||||
).
|
||||
|
||||
%% inlined_instructions(+IndexPtr, -Instrs)
|
||||
%
|
||||
% _Instrs_ are the WAM instructions corresponding to code offset _IndexPtr_.
|
||||
|
||||
inlined_instructions(IndexPtr, Listing) :-
|
||||
must_be(integer, IndexPtr),
|
||||
( IndexPtr >= 0 ->
|
||||
'$inlined_instructions'(IndexPtr, Listing)
|
||||
; throw(error(domain_error(not_less_than_zero, IndexPtr), inlined_instructions/2))
|
||||
).
|
||||
|
||||
fetch_instructions(Module, Name, Arity, Listing) :-
|
||||
must_be(atom, Module),
|
||||
|
||||
+32
-13
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
Provides predicate `dif/2`. `dif/2` is a constraint that is true only if both of its
|
||||
arguments are different terms.
|
||||
*/
|
||||
|
||||
:- module(dif, [dif/2]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
@@ -35,25 +40,39 @@ verify_attributes(Var, Value, Goals) :-
|
||||
; Goals = []
|
||||
).
|
||||
|
||||
% Probably the world's worst dif/2 implementation. I'm open to
|
||||
% suggestions for improvement.
|
||||
|
||||
%% dif(?X, ?Y).
|
||||
%
|
||||
% True iff X and Y are different terms. Unlike `\=/2`, `dif/2` is more declarative because if X and Y can
|
||||
% unify but they're not yet equal, the decision is delayed, and prevents X and Y to become equal later.
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ?- dif(a, a).
|
||||
% false.
|
||||
% ?- dif(a, b).
|
||||
% true.
|
||||
% ?- dif(X, b).
|
||||
% dif:dif(X,b).
|
||||
% ?- dif(X, b), X = b.
|
||||
% false.
|
||||
% ```
|
||||
dif(X, Y) :-
|
||||
X \== Y,
|
||||
( X \= Y -> true
|
||||
; ( term_variables(X, XVars),
|
||||
term_variables(Y, YVars),
|
||||
dif_set_variables(XVars, X, Y),
|
||||
dif_set_variables(YVars, X, Y)
|
||||
)
|
||||
; term_variables(dif(X,Y), Vars),
|
||||
dif_set_variables(Vars, X, Y)
|
||||
).
|
||||
|
||||
gather_dif_goals([]) --> [].
|
||||
gather_dif_goals([(X \== Y) | Goals]) -->
|
||||
[dif:dif(X, Y)],
|
||||
gather_dif_goals(Goals).
|
||||
gather_dif_goals(_, []) --> [].
|
||||
gather_dif_goals(V, [(X \== Y) | Goals]) -->
|
||||
( { term_variables(X-Y, [V0 | _]),
|
||||
V == V0 } ->
|
||||
[dif:dif(X, Y)]
|
||||
; []
|
||||
),
|
||||
gather_dif_goals(V, Goals).
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ get_atts(X, +dif(Goals)) },
|
||||
gather_dif_goals(Goals),
|
||||
gather_dif_goals(X, Goals),
|
||||
{ put_atts(X, -dif(_)) }.
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2018-2022 by Markus Triska (triska@metalevel.at)
|
||||
Written 2018-2023 by Markus Triska (triska@metalevel.at)
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
@@ -85,11 +85,11 @@ must_be_(list, Term) :- check_(error:ilist, list, Term).
|
||||
must_be_(type, Term) :- check_(error:type, type, Term).
|
||||
must_be_(boolean, Term) :- check_(error:boolean, boolean, Term).
|
||||
must_be_(term, Term) :-
|
||||
( \+ ground(Term) ->
|
||||
instantiation_error(must_be/2)
|
||||
; \+ acyclic_term(Term) ->
|
||||
type_error(term, Term, must_be/2)
|
||||
; true
|
||||
( acyclic_term(Term) ->
|
||||
( ground(Term) -> true
|
||||
; instantiation_error(must_be/2)
|
||||
)
|
||||
; type_error(term, Term, must_be/2)
|
||||
).
|
||||
|
||||
% We cannot use maplist(must_be(character), Cs), because library(lists)
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
:- module(ffi, [use_foreign_module/2, foreign_struct/2]).
|
||||
|
||||
/** Foreign Function Interface
|
||||
|
||||
This module contains predicates used to call native code (exposed by the C ABI).
|
||||
It uses [libffi](https://sourceware.org/libffi/) under the hood. The bridge is very simple
|
||||
and is very unsafe and should be used with care. FFI isn't the only way to communicate with
|
||||
the outside world in Prolog: sockets, pipes and HTTP may be good enough for your use case.
|
||||
|
||||
The main predicate is `use_foreign_module/2`. It takes a library name (which depending on the
|
||||
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
|
||||
function is defined by its name, a list of the type of the arguments, and the return argument.
|
||||
|
||||
Types available are: `sint8`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`,
|
||||
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||
with `foreign_struct/2`.
|
||||
|
||||
After that, each function on the lists maps to a predicate created in the ffi module which
|
||||
are used to call the native code.
|
||||
The predicate takes the functor name after the function name. Then, the arguments are the input
|
||||
arguments followed by a return argument. However, functions with return type `void` or `bool`
|
||||
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
|
||||
on the return value on the native side.
|
||||
|
||||
```
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
||||
|
||||
```
|
||||
?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]).
|
||||
```
|
||||
|
||||
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
|
||||
|
||||
```
|
||||
?- ffi:'InitWindow'(800, 600, "Scryer Prolog + Raylib").
|
||||
```
|
||||
|
||||
And a new window should pop up!
|
||||
*/
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% foreign_struct(+Name, +Elements).
|
||||
%
|
||||
% Defines a new struct type with name Name, composed of the elements Elements, which is a list
|
||||
% of other types.
|
||||
%
|
||||
% The name of the types doesn't matter, but the order of Elements must match the ones in the
|
||||
% native code.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- foreign_struct(color, [uint8, uint8, uint8, uint8]).
|
||||
% ```
|
||||
foreign_struct(Name, Elements) :-
|
||||
'$define_foreign_struct'(Name, Elements).
|
||||
|
||||
use_foreign_module(LibName, Predicates) :-
|
||||
'$load_foreign_lib'(LibName, Predicates),
|
||||
maplist(assert_predicate, Predicates).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
PredicateDefinition =.. [Name, Inputs, void],
|
||||
length(Inputs, NumInputs),
|
||||
functor(Head, Name, NumInputs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
'$foreign_call'(Name, TermList, _),!
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
PredicateDefinition =.. [Name, Inputs, bool],
|
||||
length(Inputs, NumInputs),
|
||||
functor(Head, Name, NumInputs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
'$foreign_call'(Name, TermList, 1),!
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
PredicateDefinition =.. [Name, Inputs, Return],
|
||||
\+ member(Return, [void, bool]),
|
||||
length(Inputs, NumInputs),
|
||||
NumArgs is NumInputs + 1,
|
||||
functor(Head, Name, NumArgs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
lists:append(TermListInputs, [TermListReturn], TermList),
|
||||
'$foreign_call'(Name, TermListInputs, TermListReturn),!
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
+122
-44
@@ -1,3 +1,22 @@
|
||||
/** Predicates for reasoning about files and directories.
|
||||
|
||||
In this library, directories and files are represented as
|
||||
_lists of characters_. This is an ideal representation:
|
||||
|
||||
* Lists of characters can be conveniently reasoned about with DCGs
|
||||
and built-in Prolog predicates from `library(lists)`. This alone
|
||||
is already a very compelling argument to use them.
|
||||
* Other Scryer libraries such as `library(http/http_open)` also already
|
||||
use lists of characters to represent paths.
|
||||
* File names are mostly ephemeral, so it is good for efficiency
|
||||
that they can quickly allocated transiently on the heap, leaving the
|
||||
atom table mostly unaffected. Indexing is almost never needed
|
||||
for file names. If needed, it should be added to the engine.
|
||||
* The previous point is also good for security, since the system
|
||||
leaves little trace of which files were even accessed.
|
||||
* Scryer Prolog represents lists of characters extremely compactly.
|
||||
*/
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2022 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
@@ -51,8 +70,9 @@
|
||||
file_exists/1,
|
||||
directory_exists/1,
|
||||
delete_file/1,
|
||||
rename_file/2,
|
||||
delete_directory/1,
|
||||
rename_file/2,
|
||||
file_copy/2,
|
||||
delete_directory/1,
|
||||
make_directory/1,
|
||||
make_directory_path/1,
|
||||
working_directory/2,
|
||||
@@ -67,41 +87,82 @@
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
%% directory_files(+Directory, -Files).
|
||||
%
|
||||
% Returns the list of files *and* directories available at a specific
|
||||
% directory in the current system.
|
||||
|
||||
directory_files(Directory, Files) :-
|
||||
must_be(chars, Directory),
|
||||
can_be(list, Files),
|
||||
'$directory_files'(Directory, Files).
|
||||
|
||||
%% file_size(+File, -Size).
|
||||
%
|
||||
% Returns the size (in bytes) of a file. The file must exist.
|
||||
|
||||
file_size(File, Size) :-
|
||||
file_must_exist(File, file_size/2),
|
||||
can_be(integer, Size),
|
||||
'$file_size'(File, Size).
|
||||
|
||||
%% file_exists(+File).
|
||||
%
|
||||
% Succeeds if File is a file that exists in the current system.
|
||||
file_exists(File) :-
|
||||
must_be(chars, File),
|
||||
'$file_exists'(File).
|
||||
|
||||
%% directory_exists(+Directory).
|
||||
%
|
||||
% Succeeds if Directory is a directory that exists in the current system.
|
||||
directory_exists(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
'$directory_exists'(Directory).
|
||||
|
||||
%% make_directory(+Directory).
|
||||
%
|
||||
% Succeeds if it creates a new directory named Directory in the current system.
|
||||
% If you want to create a nested directory, use `make_directory_path/1`.
|
||||
make_directory(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
'$make_directory'(Directory).
|
||||
|
||||
%% make_directory_path(+Directory).
|
||||
%
|
||||
% Similar to `make_directory/1` but recursively creates directories if they're missing.
|
||||
% Equivalent to mkdir -p in Unix.
|
||||
make_directory_path(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
'$make_directory_path'(Directory).
|
||||
|
||||
%% delete_file(+File).
|
||||
%
|
||||
% Succeeds if deletes File from the current system.
|
||||
delete_file(File) :-
|
||||
file_must_exist(File, delete_file/1),
|
||||
'$delete_file'(File).
|
||||
|
||||
%% rename_file(+File, +Renamed).
|
||||
%
|
||||
% Succeeds if File is renamed to Renamed
|
||||
rename_file(File, Renamed) :-
|
||||
file_must_exist(File, rename_file/2),
|
||||
must_be(chars, Renamed),
|
||||
'$rename_file'(File, Renamed).
|
||||
|
||||
%% file_copy(+File, +Copied).
|
||||
%
|
||||
% Succeeds if File is copied to Copied
|
||||
file_copy(File, Copied) :-
|
||||
file_must_exist(File, file_copy/2),
|
||||
must_be(chars, Copied),
|
||||
'$file_copy'(File, Copied).
|
||||
|
||||
%% delete_directory(+Directory).
|
||||
%
|
||||
% Succeeds if Directory is deleted from the current system.
|
||||
% Directory must be empty.
|
||||
delete_directory(Directory) :-
|
||||
directory_must_exist(Directory, delete_directory/1),
|
||||
must_be(chars, Directory),
|
||||
@@ -117,31 +178,31 @@ directory_must_exist(Directory, Context) :-
|
||||
; throw(error(existence_error(directory, Directory), Context))
|
||||
).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Dir0 is the current working directory, and the working directory
|
||||
is changed to Dir.
|
||||
|
||||
Use working_directory(Ds, Ds) to determine the current working directory,
|
||||
and leave it as is.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% workind_directory(Dir0, Dir).
|
||||
%
|
||||
% Dir0 is the current working directory, and the working directory
|
||||
% is changed to Dir.
|
||||
%
|
||||
% Use `working_directory/2` to determine the current working directory,
|
||||
% and leave it as is.
|
||||
|
||||
working_directory(Dir0, Dir) :-
|
||||
can_be(list, Dir0),
|
||||
can_be(list, Dir),
|
||||
'$working_directory'(Dir0, Dir).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
True iff Cs is the canonical, absolute path of Ps.
|
||||
|
||||
All intermediate components are normalized, and all symbolic links
|
||||
are resolved.
|
||||
|
||||
The predicate fails in the following situations, though not
|
||||
necessarily *only* in these cases:
|
||||
|
||||
1. Ps is a path that does not exist.
|
||||
2. A non-final component in Ps is not a directory.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% path_canonical(Ps, Cs).
|
||||
%
|
||||
% True iff Cs is the canonical, absolute path of Ps.
|
||||
%
|
||||
% All intermediate components are normalized, and all symbolic links
|
||||
% are resolved.
|
||||
%
|
||||
% The predicate fails in the following situations, though not
|
||||
% necessarily *only* in these cases:
|
||||
%
|
||||
% 1. Ps is a path that does not exist.
|
||||
% 2. A non-final component in Ps is not a directory.
|
||||
|
||||
path_canonical(Ps, Cs) :-
|
||||
must_be(chars, Ps),
|
||||
@@ -155,12 +216,27 @@ path_canonical(Ps, Cs) :-
|
||||
For two time stamps A and B, if A precedes B, then A @< B holds.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% file_modification_time(+File, -T).
|
||||
%
|
||||
% For a file File that must exist, it returns a time stamp T with the modification time
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_modification_time(File, T) :-
|
||||
file_time_(File, modification, T).
|
||||
|
||||
%% file_access_time(+File, -T).
|
||||
%
|
||||
% For a file File that must exist, it returns a time stamp T with the access time
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_access_time(File, T) :-
|
||||
file_time_(File, access, T).
|
||||
|
||||
%% file_creation_time(+File, -T).
|
||||
%
|
||||
% For a file File that must exist, it returns a time stamp T with the creation time
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_creation_time(File, T) :-
|
||||
file_time_(File, creation, T).
|
||||
|
||||
@@ -170,29 +246,31 @@ file_time_(File, Which, T) :-
|
||||
read_from_chars(T0, T).
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
path_segments(Ps, Segments): True iff Segments are the segments of Ps.
|
||||
|
||||
Segments is the list of components of the path Ps that are
|
||||
separated by the platform-specific directory separator. Each
|
||||
segment is a list of characters.
|
||||
|
||||
At least one of the arguments must be instantiated.
|
||||
|
||||
Examples:
|
||||
|
||||
?- path_segments("/hello/there", Segments).
|
||||
Segments = [[],"hello","there"].
|
||||
|
||||
?- path_segments(Path, ["hello","there"]).
|
||||
Path = "hello/there".
|
||||
|
||||
|
||||
To obtain the platform-specific directory separator, you can use:
|
||||
|
||||
?- path_segments(Separator, ["",""]).
|
||||
Separator = "/".
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% path_segments(Ps, Segments).
|
||||
%
|
||||
% True iff Segments are the segments of Ps.
|
||||
%
|
||||
% Segments is the list of components of the path Ps that are
|
||||
% separated by the platform-specific directory separator. Each
|
||||
% segment is a list of characters.
|
||||
%
|
||||
% At least one of the arguments must be instantiated.
|
||||
%
|
||||
% Examples:
|
||||
%
|
||||
% ```
|
||||
% ?- path_segments("/hello/there", Segments).
|
||||
% Segments = [[],"hello","there"].
|
||||
% ?- path_segments(Path, ["hello","there"]).
|
||||
% Path = "hello/there".
|
||||
% ```
|
||||
%
|
||||
% To obtain the platform-specific directory separator, you can use:
|
||||
%
|
||||
% ```
|
||||
% ?- path_segments(Separator, ["",""]).
|
||||
% Separator = "/".
|
||||
% ```
|
||||
|
||||
path_segments(Path, Segments) :-
|
||||
'$directory_separator'(Sep),
|
||||
|
||||
+92
-78
@@ -1,83 +1,17 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2021, 2022 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
This library provides the nonterminal format_//2 to describe
|
||||
formatted strings. format/[2,3] are provided for impure output.
|
||||
|
||||
Usage:
|
||||
======
|
||||
|
||||
phrase(format_(FormatString, Arguments), Ls)
|
||||
|
||||
format_//2 describes a list of characters Ls that are formatted
|
||||
according to FormatString. FormatString is a string (i.e.,
|
||||
a list of characters) that specifies the layout of Ls.
|
||||
The characters in FormatString are used literally, except
|
||||
for the following tokens with special meaning:
|
||||
|
||||
~w use the next available argument from Arguments here
|
||||
~q use the next argument here, formatted as by writeq/1
|
||||
~a use the next argument here, which must be an atom
|
||||
~s use the next argument here, which must be a string
|
||||
~d use the next argument here, which must be an integer
|
||||
~f use the next argument here, a floating point number
|
||||
~Nf where N is an integer: format the float argument
|
||||
using N digits after the decimal point
|
||||
~Nd like ~d, placing the last N digits after a decimal point;
|
||||
if N is 0 or omitted, no decimal point is used.
|
||||
~ND like ~Nd, separating digits to the left of the decimal point
|
||||
in groups of three, using the character "," (comma)
|
||||
~NU like ~ND, using "_" (underscore) to separate groups of digits
|
||||
~NL format an integer so that at most N digits appear on a line.
|
||||
If N is 0 or omitted, it defaults to 72.
|
||||
~Nr where N is an integer between 2 and 36: format the
|
||||
next argument, which must be an integer, in radix N.
|
||||
The characters "a" to "z" are used for radices 10 to 36.
|
||||
If N is omitted, it defaults to 8 (octal).
|
||||
~NR like ~Nr, except that "A" to "Z" are used for radices > 9
|
||||
~| place a tab stop at this position
|
||||
~N| where N is an integer: place a tab stop at text column N
|
||||
~N+ where N is an integer: place a tab stop N characters
|
||||
after the previous tab stop (or start of line)
|
||||
~t distribute spaces evenly between the two closest tab stops
|
||||
~`Ct like ~t, use character C instead of spaces to fill the space
|
||||
~n newline
|
||||
~Nn N newlines
|
||||
~i ignore the next argument
|
||||
~~ the literal ~
|
||||
|
||||
Instead of ~N, you can write ~* to use the next argument from Arguments
|
||||
as the numeric argument.
|
||||
|
||||
The predicate format/2 is like format_//2, except that it outputs
|
||||
the text on the terminal instead of describing it declaratively.
|
||||
|
||||
format/3, used as format(Stream, FormatString, Arguments), outputs
|
||||
the described string to the given Stream. If Stream is a binary
|
||||
stream, then the code of each emitted character must be in 0..255.
|
||||
|
||||
If at all possible, format_//2 should be used, to stress pure parts
|
||||
that enable easy testing etc. If necessary, you can emit the list Ls
|
||||
with maplist(put_char, Ls) or, much faster, with format("~s", [Ls]).
|
||||
Ideally, however, you use phrase_to_file/[2,3] or phrase_to_stream/2
|
||||
from library(pio) to write the described list directly to a file
|
||||
or stream, respectively: phrase_to_stream(format_(..., [...]), S).
|
||||
The advantage of this is that an ideal implementation writes
|
||||
the characters as they become known, without manifesting the list.
|
||||
|
||||
The entire library only works if the Prolog flag double_quotes
|
||||
is set to chars, the default value in Scryer Prolog. This should
|
||||
also stay that way, to encourage a sensible environment.
|
||||
|
||||
Example:
|
||||
|
||||
?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
|
||||
%@ Cs = "hello\n......there!".
|
||||
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** This library provides the nonterminal `format_//2` to describe
|
||||
formatted strings. `format/[2,3]` are provided for _impure_ output.
|
||||
|
||||
The entire library only works if the Prolog flag `double_quotes`
|
||||
is set to `chars`, the default value in Scryer Prolog. This should
|
||||
also stay that way, to encourage a sensible environment.
|
||||
*/
|
||||
|
||||
:- module(format, [format_//2,
|
||||
format/2,
|
||||
format/3,
|
||||
@@ -94,6 +28,61 @@
|
||||
:- use_module(library(between)).
|
||||
:- use_module(library(pio)).
|
||||
|
||||
%% format_(+FormatString, +Arguments)//
|
||||
%
|
||||
% Usage:
|
||||
%
|
||||
% ```
|
||||
% phrase(format_(FormatString, Arguments), Ls)
|
||||
% ```
|
||||
%
|
||||
% `format_//2` describes a list of characters Ls that are formatted
|
||||
% according to FormatString. FormatString is a string (i.e., a list of
|
||||
% characters) that specifies the layout of Ls. The characters in
|
||||
% FormatString are used literally, except for the following tokens
|
||||
% with special meaning:
|
||||
%
|
||||
% | `~w` | use the next available argument from Arguments here |
|
||||
% | `~q` | use the next argument here, formatted as by `writeq/1` |
|
||||
% | `~a` | use the next argument here, which must be an atom |
|
||||
% | `~s` | use the next argument here, which must be a string |
|
||||
% | `~d` | use the next argument here, which must be an integer |
|
||||
% | `~f` | use the next argument here, a floating point number |
|
||||
% | `~Nf` | where N is an integer: format the float argument |
|
||||
% | | using N digits after the decimal point |
|
||||
% | `~Nd` | like ~d, placing the last N digits after a decimal point; |
|
||||
% | | if N is 0 or omitted, no decimal point is used. |
|
||||
% | `~ND` | like ~Nd, separating digits to the left of the decimal point |
|
||||
% | | in groups of three, using the character "," (comma) |
|
||||
% | `~NU` | like ~ND, using "_" (underscore) to separate groups of digits |
|
||||
% | `~NL` | format an integer so that at most N digits appear on a line. |
|
||||
% | | If N is 0 or omitted, it defaults to 72. |
|
||||
% | `~Nr` | where N is an integer between 2 and 36: format the |
|
||||
% | | next argument, which must be an integer, in radix N. |
|
||||
% | | The characters "a" to "z" are used for radices 10 to 36. |
|
||||
% | | If N is omitted, it defaults to 8 (octal). |
|
||||
% | `~NR` | like ~Nr, except that "A" to "Z" are used for radices > 9 |
|
||||
% | `~|` | place a tab stop at this position |
|
||||
% | `~N|` | where N is an integer: place a tab stop at text column N |
|
||||
% | `~N+` | where N is an integer: place a tab stop N characters |
|
||||
% | | after the previous tab stop (or start of line) |
|
||||
% | `~t` | distribute spaces evenly between the two closest tab stops |
|
||||
% | ``~`Ct`` | like ~t, use character C instead of spaces to fill the space |
|
||||
% | `~n` | newline |
|
||||
% | `~Nn` | N newlines |
|
||||
% | `~i` | ignore the next argument |
|
||||
% | `~~` | the literal ~ |
|
||||
%
|
||||
% Instead of `~N`, you can write `~*` to use the next argument from
|
||||
% Arguments as the numeric argument.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
|
||||
% Cs = "hello\n......there!".
|
||||
% ```
|
||||
|
||||
format_(Fs, Args) -->
|
||||
{ must_be(list, Fs),
|
||||
must_be(list, Args),
|
||||
@@ -414,10 +403,32 @@ digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
||||
Impure I/O, implemented as a small wrapper over format_//2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% format(+Fs, +Args)
|
||||
%
|
||||
% The predicate `format/2` is like `format_//2`, except that it
|
||||
% outputs the text on the terminal instead of describing it
|
||||
% declaratively as a list of characters.
|
||||
%
|
||||
% If at all possible, `format_//2` should be used, to stress pure
|
||||
% parts that enable easy testing etc. If necessary, you can emit the
|
||||
% described list of characters `Ls` with `maplist(put_char, Ls)` or,
|
||||
% much faster, with `format("~s", [Ls])`. Ideally, however, you use
|
||||
% `phrase_to_file/[2,3]` or `phrase_to_stream/2` from `library(pio)`
|
||||
% to write the described list directly to a file or stream,
|
||||
% respectively: `phrase_to_stream(format_(..., [...]), S)`. The
|
||||
% advantage of this is that an ideal implementation writes the
|
||||
% characters as they become known, without manifesting the list.
|
||||
|
||||
format(Fs, Args) :-
|
||||
current_output(Stream),
|
||||
format(Stream, Fs, Args).
|
||||
|
||||
%% format(Stream, FormatString, Arguments)
|
||||
%
|
||||
% Output the described string to the given Stream. If Stream is a
|
||||
% binary stream, then the code of each emitted character must be in
|
||||
% 0..255.
|
||||
|
||||
format(Stream, Fs, Args) :-
|
||||
phrase_to_stream(format_(Fs, Args), Stream),
|
||||
flush_output(Stream).
|
||||
@@ -486,11 +497,14 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
|
||||
In the eventual library organization, portray_clause/1 and
|
||||
related predicates may be placed in their own dedicated library.
|
||||
|
||||
portray_clause/1 is useful for printing solutions in such a way
|
||||
that they can be read back with read/1.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
%% portray_clause(+Term)
|
||||
%
|
||||
% `portray_clause/1` is useful for printing solutions in such a way
|
||||
% that they can be read back with `read/1`.
|
||||
|
||||
portray_clause(Term) :-
|
||||
current_output(Out),
|
||||
portray_clause(Out, Term).
|
||||
|
||||
+13
-1
@@ -1,5 +1,8 @@
|
||||
:- module(freeze, [freeze/2]).
|
||||
|
||||
/** Provides the constraint `freeze/2`.
|
||||
*/
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
@@ -19,6 +22,15 @@ verify_attributes(Var, Other, Goals) :-
|
||||
).
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
%% freeze(Var, Goal)
|
||||
%
|
||||
% Schedules Goal to be executed when Var is instantiated. This can
|
||||
% be useful to observe the exact moment a variable becomes bound to a
|
||||
% more concrete term, for example when creating animations of search
|
||||
% processes. Higher-level constructs such as `phrase_from_file/2` can
|
||||
% also be implemented with `freeze/2`, by scheduling a goal that
|
||||
% reads additional data from a file as soon as it is needed.
|
||||
|
||||
freeze(X, Goal) :-
|
||||
put_atts(Fresh, frozen(Goal)),
|
||||
Fresh = X.
|
||||
@@ -26,5 +38,5 @@ freeze(X, Goal) :-
|
||||
attribute_goals(Var) -->
|
||||
{ get_atts(Var, frozen(Goals)),
|
||||
put_atts(Var, -frozen(_)) },
|
||||
[freeze(Var, Goals)].
|
||||
[freeze:freeze(Var, Goals)].
|
||||
|
||||
|
||||
+8
-8
@@ -19,14 +19,14 @@ gensym(Base, Unique) :-
|
||||
must_be(var, Unique),
|
||||
atom_si(Base),
|
||||
gensym_key(Base, BaseKey),
|
||||
( bb_get(BaseKey, UniqueID0) ->
|
||||
UniqueID is UniqueID0 + 1,
|
||||
bb_put(BaseKey, UniqueID),
|
||||
append_id(Base, UniqueID, Unique)
|
||||
; bb_put(BaseKey, 1),
|
||||
append_id(Base, 1, Unique)
|
||||
).
|
||||
( bb_get(BaseKey, UniqueID0) -> true
|
||||
; UniqueID0 = 0
|
||||
),
|
||||
UniqueID is UniqueID0 + 1,
|
||||
append_id(Base, UniqueID, Unique),
|
||||
bb_put(BaseKey, UniqueID).
|
||||
|
||||
reset_gensym(Base) :-
|
||||
atom_si(Base),
|
||||
bb_put(Base, 0).
|
||||
gensym_key(Base, BaseKey),
|
||||
bb_put(BaseKey, 0).
|
||||
|
||||
+27
-22
@@ -1,34 +1,39 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2022 by Adrián Arroyo Calle (adrian.arroyocalle@gmail.com)
|
||||
Part of Scryer Prolog.
|
||||
*/
|
||||
|
||||
http_open(+Address, -Stream, +Options)
|
||||
======================================
|
||||
/** Make HTTP requests.
|
||||
|
||||
Yields Stream to read the body of an HTTP reply from Address.
|
||||
Address is a list of characters, and includes the method. Both HTTP
|
||||
and HTTPS are supported.
|
||||
|
||||
Options supported:
|
||||
|
||||
* method(+Method): Sets the HTTP method of the call. Method can be get (default), head, delete, post, put or patch.
|
||||
* data(+Data): Data to be sent in the request. Useful for POST, PUT and PATCH operations.
|
||||
* size(-Size): Unifies with the value of the Content-Length header
|
||||
* request_headers(+RequestHeaders): Headers to be used in the request
|
||||
* headers(-ListHeaders): Unifies with a list with all headers returned in the response
|
||||
* status_code(-Code): Unifies with the status code of the request (200, 201, 404, ...)
|
||||
|
||||
Example:
|
||||
|
||||
?- http_open("https://github.com/mthom/scryer-prolog", S, []).
|
||||
%@ S = '$stream'(0x7fcfc9e00f00).
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
This library contains the predicate `http_open/3` which allows you to perform HTTP(S) calls.
|
||||
Useful for making API calls, or parsing websites. It uses Hyper underneath.
|
||||
*/
|
||||
|
||||
:- module(http_open, [http_open/3]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
%% http_open(+Address, -Stream, +Options).
|
||||
%
|
||||
% Yields Stream to read the body of an HTTP reply from Address.
|
||||
% Address is a list of characters, and includes the method. Both HTTP
|
||||
% and HTTPS are supported.
|
||||
%
|
||||
% Options supported:
|
||||
%
|
||||
% * `method(+Method)`: Sets the HTTP method of the call. Method can be `get` (default), `head`, `delete`, `post`, `put` or `patch`.
|
||||
% * `data(+Data)`: Data to be sent in the request. Useful for POST, PUT and PATCH operations.
|
||||
% * `size(-Size)`: Unifies with the value of the Content-Length header
|
||||
% * `request_headers(+RequestHeaders)`: Headers to be used in the request
|
||||
% * `headers(-ListHeaders)`: Unifies with a list with all headers returned in the response
|
||||
% * `status_code(-Code)`: Unifies with the status code of the request (200, 201, 404, ...)
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- http_open("https://www.example.com", S, []), get_n_chars(S, N, HTML).
|
||||
% S = '$stream'(0x7fb548001be8), N = 1256, HTML = "<!doctype html>\n<ht ...".
|
||||
% ```
|
||||
http_open(Address, Response, Options) :-
|
||||
parse_http_options(Options, OptionValues),
|
||||
( member(method(Method), OptionValues) -> true; Method = get),
|
||||
@@ -65,4 +70,4 @@ parse_http_options_(request_headers(Headers), request_headers(Headers)) :-
|
||||
|
||||
parse_http_options_(size(Size), size(Size)).
|
||||
parse_http_options_(status_code(Code), status_code(Code)).
|
||||
parse_http_options_(headers(Headers), headers(Headers)).
|
||||
parse_http_options_(headers(Headers), headers(Headers)).
|
||||
|
||||
+90
-51
@@ -1,51 +1,55 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
|
||||
Updated in March 2022 by Adrián Arroyo to use the Hyper backend
|
||||
Part of Scryer Prolog
|
||||
Part of Scryer Prolog.
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
*/
|
||||
|
||||
This library provides an starting point to build HTTP server based applications.
|
||||
It is based on Hyper, which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
|
||||
some advanced features that Hyper provides are still not accesible.
|
||||
/** This library provides an starting point to build HTTP server based applications.
|
||||
It is based on [Hyper](https://hyper.rs/), which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
|
||||
some advanced features that Hyper provides are still not accesible.
|
||||
|
||||
Usage
|
||||
==========
|
||||
The main predicate of the library is http_listen/2, which needs a port number
|
||||
(usually 80) and a list of handlers. A handler is a compound term with the functor
|
||||
as one HTTP method (in lowercase) and followed by a Route Match and a predicate
|
||||
which will handle the call.
|
||||
## Usage
|
||||
|
||||
text_handler(Request, Response) :-
|
||||
http_status_code(Response, 200),
|
||||
http_body(Response, text("Welcome to Scryer Prolog!")).
|
||||
The main predicate of the library is `http_listen/2`, which needs a port number
|
||||
(usually 80) and a list of handlers. A handler is a compound term with the functor
|
||||
as one HTTP method (in lowercase) and followed by a Route Match and a predicate
|
||||
which will handle the call.
|
||||
|
||||
parameter_handler(User, Request, Response) :-
|
||||
http_body(Response, text(User)).
|
||||
```
|
||||
text_handler(Request, Response) :-
|
||||
http_status_code(Response, 200),
|
||||
http_body(Response, text("Welcome to Scryer Prolog!")).
|
||||
|
||||
http_listen(7890, [
|
||||
get(echo, text_handler), % GET /echo
|
||||
post(user/User, parameter_handler(User)) % POST /user/<User>
|
||||
]).
|
||||
parameter_handler(User, Request, Response) :-
|
||||
http_body(Response, text(User)).
|
||||
|
||||
Every handler predicate will have at least 2-arity, with Request and Response.
|
||||
Although you can work directly with http_request and http_response terms, it is
|
||||
recommeded to use the helper predicates, which are easier to understand and cleaner:
|
||||
- http_headers(Response/Request, Headers)
|
||||
- http_status_code(Responde, StatusCode)
|
||||
- http_body(Response/Request, text(Body))
|
||||
- http_body(Response/Request, binary(Body))
|
||||
- http_body(Request, form(Form))
|
||||
- http_body(Response, file(Filename))
|
||||
- http_redirect(Response, Url)
|
||||
- http_query(Request, QueryName, QueryValue)
|
||||
http_listen(7890, [
|
||||
get(echo, text_handler), % GET /echo
|
||||
post(user/User, parameter_handler(User)) % POST /user/<User>
|
||||
]).
|
||||
```
|
||||
|
||||
Every handler predicate will have at least 2-arity, with Request and Response.
|
||||
Although you can work directly with `http_request` and `http_response` terms, it is
|
||||
recommeded to use the helper predicates, which are easier to understand and cleaner:
|
||||
|
||||
- `http_headers(Response/Request, Headers)`
|
||||
- `http_status_code(Responde, StatusCode)`
|
||||
- `http_body(Response/Request, text(Body))`
|
||||
- `http_body(Response/Request, binary(Body))`
|
||||
- `http_body(Request, form(Form))`
|
||||
- `http_body(Response, file(Filename))`
|
||||
- `http_redirect(Response, Url)`
|
||||
- `http_query(Request, QueryName, QueryValue)`
|
||||
|
||||
Some things that are still missing:
|
||||
|
||||
Some things that are still missing:
|
||||
- Read forms in multipart format
|
||||
- HTTP Basic Auth
|
||||
- Session handling via cookies
|
||||
- HTML Templating
|
||||
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
- HTML Templating (but you can use [Teruel](https://github.com/aarroyoc/teruel/), [Marquete](https://github.com/aarroyoc/marquete/) or [Djota](https://github.com/aarroyoc/djota) for that)
|
||||
*/
|
||||
|
||||
|
||||
:- module(http_server, [
|
||||
@@ -68,6 +72,11 @@
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(time)).
|
||||
|
||||
%% http_listen(+Port, +Handlers).
|
||||
%
|
||||
% Listens for HTTP connections on port Port. Each handler on the list Handlers should be of the form: `HttpVerb(PathUnification, Predicate)`.
|
||||
% For example: `get(user/User, get_info(User))` will match an HTTP request that is a GET, the path unifies with /user/User (where User is a variable)
|
||||
% and it will call `get_info` with three arguments: an `http_request` term, an `http_response` term and User.
|
||||
http_listen(Port, Module:Handlers0) :-
|
||||
must_be(integer, Port),
|
||||
must_be(list, Handlers0),
|
||||
@@ -112,37 +121,44 @@ http_loop(HttpListener, Handlers) :-
|
||||
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||
call_cleanup(
|
||||
format(ResponseStream, "~s", [ResponseText]),
|
||||
close(ResponseStream)
|
||||
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
|
||||
open(stream(ResponseStream0), write, ResponseStream, [type(text)]),
|
||||
catch(
|
||||
call_cleanup(format(ResponseStream, "~s", [ResponseText]),close(ResponseStream)),
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||
call_cleanup(
|
||||
format(ResponseStream, "~s", [ResponseBytes]),
|
||||
close(ResponseStream)
|
||||
catch(
|
||||
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||
call_cleanup(
|
||||
setup_call_cleanup(
|
||||
open(Filename, read, FileStream, [type(binary)]),
|
||||
(
|
||||
get_n_chars(FileStream, _, FileCs),
|
||||
format(ResponseStream, "~s", [FileCs])
|
||||
catch(
|
||||
call_cleanup(
|
||||
setup_call_cleanup(
|
||||
open(Filename, read, FileStream, [type(binary)]),
|
||||
(
|
||||
get_n_chars(FileStream, _, FileCs),
|
||||
format(ResponseStream, "~s", [FileCs])
|
||||
),
|
||||
close(FileStream)
|
||||
),
|
||||
close(FileStream)
|
||||
close(ResponseStream)
|
||||
),
|
||||
close(ResponseStream)
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
|
||||
|
||||
default(Var, Default, Out) :-
|
||||
(var(Var) -> Out = Default
|
||||
@@ -206,9 +222,21 @@ string_without(Not, [Char|String]) -->
|
||||
string_without(_, []) -->
|
||||
[].
|
||||
|
||||
%% http_headers(?Request_Response, ?Headers).
|
||||
%
|
||||
% True iff `Request_Response` is a request or response with headers Headers. Can be used both to get headers (usually in from a request)
|
||||
% and to add headers (usually in a response).
|
||||
http_headers(http_request(Headers, _, _), Headers).
|
||||
http_headers(http_response(_, _, Headers), Headers).
|
||||
|
||||
%% http_body(?Request_Response, ?Body).
|
||||
%
|
||||
% True iff Body is the body of the request or response. A body can be of the following types:
|
||||
%
|
||||
% * `bytes(Bytes)` for both requests and responses, interprets the body as bytes
|
||||
% * `text(Bytes)` for both requests and responses, interprets the body as text
|
||||
% * `form(Form)` only for requests, interprets the body as an `application/x-www-form-urlencoded` form.
|
||||
% * `file(File)` only for responses, interprets the body as the content of a file (useful to send static files).
|
||||
http_body(http_request(_, stream(StreamBody), _), bytes(BytesBody)) :- get_n_chars(StreamBody, _, BytesBody).
|
||||
http_body(http_request(_, stream(StreamBody), _), text(TextBody)) :- get_n_chars(StreamBody, _, TextBody).
|
||||
http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
|
||||
@@ -218,8 +246,19 @@ http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
|
||||
http_body(http_request(_, Body, _), Body).
|
||||
http_body(http_response(_, Body, _), Body).
|
||||
|
||||
%% http_status_code(?Response, ?StatusCode).
|
||||
%
|
||||
% True iff the status code of the response Response unifies with StatusCode.
|
||||
http_status_code(http_response(StatusCode, _, _), StatusCode).
|
||||
|
||||
%% http_redirect(-Response, +Uri).
|
||||
%
|
||||
% True iff Response is a response that redirects the user to the uri Uri.
|
||||
http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
|
||||
|
||||
%% http_query(+Request, ?Key, ?Value).
|
||||
%
|
||||
% True iff there's a query in request Request with key Key and value Value.
|
||||
http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
|
||||
|
||||
parse_queries([Key-Value|Queries]) -->
|
||||
|
||||
+146
-14
@@ -1,3 +1,9 @@
|
||||
/** Useful general predicates that are not ISO standard yet
|
||||
|
||||
Predicates available here are similar to the ones defined in builtin.pl,
|
||||
but they're not part of the ISO Prolog standard at the moment.
|
||||
*/
|
||||
|
||||
:- module(iso_ext, [bb_b_put/2,
|
||||
bb_get/2,
|
||||
bb_put/2,
|
||||
@@ -9,9 +15,10 @@
|
||||
partial_string_tail/2,
|
||||
setup_call_cleanup/3,
|
||||
call_nth/2,
|
||||
countall/2,
|
||||
copy_term_nat/2,
|
||||
asserta/2,
|
||||
assertz/2]).
|
||||
asserta/2,
|
||||
assertz/2]).
|
||||
|
||||
:- use_module(library(error), [can_be/2,
|
||||
domain_error/3,
|
||||
@@ -22,25 +29,82 @@
|
||||
|
||||
:- meta_predicate(forall(0, 0)).
|
||||
|
||||
%% forall(Generate, Test).
|
||||
%
|
||||
% For all bindings possible by Generate, Test must be true.
|
||||
%
|
||||
% In this example, it checks that all numbers are even:
|
||||
%
|
||||
% ```
|
||||
% ?- Ns = [2,4,6], forall(member(N, Ns), 0 is N mod 2).
|
||||
% Ns = [2,4,6].
|
||||
% ```
|
||||
forall(Generate, Test) :-
|
||||
\+ (Generate, \+ Test).
|
||||
|
||||
%% (non-)backtrackable global variables.
|
||||
% (non-)backtrackable global variables.
|
||||
|
||||
%% bb_put(+Key, +Value).
|
||||
%
|
||||
% Sets a global variable named Key (must be an atom) with value Value.
|
||||
% The global variable isn't backtrackable. Check `bb_b_put/2` for the
|
||||
% backtrackable version.
|
||||
%
|
||||
% ```
|
||||
% ?- bb_put(city, "Valladolid").
|
||||
% true.
|
||||
% ?- bb_get(city, X).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
%
|
||||
% In this example one can understand the difference between `bb_put/2` and
|
||||
% `bb_b_put/2`:
|
||||
%
|
||||
% ```
|
||||
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Salamanca".
|
||||
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
bb_put(Key, Value) :-
|
||||
( atom(Key) ->
|
||||
'$store_global_var'(Key, Value)
|
||||
; type_error(atom, Key, bb_put/2)
|
||||
).
|
||||
|
||||
%% backtrackable global variables.
|
||||
% backtrackable global variables.
|
||||
|
||||
%% bb_b_put(+Key, +Value).
|
||||
%
|
||||
% Sets a global variable named Key (must be an atom) with value Value.
|
||||
% The global variable is backtrackable. Check `bb_put/2` for the
|
||||
% non-backtrackable version.
|
||||
%
|
||||
% ```
|
||||
% ?- bb_b_put(city, "Valladolid").
|
||||
% true.
|
||||
% ?- bb_get(city, X).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
%
|
||||
% In this example one can understand the difference between `bb_put/2` and
|
||||
% `bb_b_put/2`:
|
||||
%
|
||||
% ```
|
||||
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Salamanca".
|
||||
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||
% X = "Valladolid".
|
||||
% ```
|
||||
bb_b_put(Key, Value) :-
|
||||
( atom(Key) ->
|
||||
'$store_backtrackable_global_var'(Key, Value)
|
||||
; type_error(atom, Key, bb_b_put/2)
|
||||
).
|
||||
|
||||
%% bb_get(+Key, -Value).
|
||||
%
|
||||
% Gets the value Value of a global variable named Key (must be an atom)
|
||||
bb_get(Key, Value) :-
|
||||
( atom(Key) ->
|
||||
'$fetch_global_var'(Key, Value)
|
||||
@@ -52,17 +116,30 @@ bb_get(Key, Value) :-
|
||||
|
||||
:- meta_predicate(call_cleanup(0, 0)).
|
||||
|
||||
%% call_cleanup(Goal, Cleanup).
|
||||
%
|
||||
% Executes Goal and then, either on success or failure, executes Cleanup.
|
||||
% The success or failure of Cleanup is ignored and choice points created inside are destroyed.
|
||||
call_cleanup(G, C) :- setup_call_cleanup(true, G, C).
|
||||
|
||||
:- meta_predicate(setup_call_cleanup(0, 0, 0)).
|
||||
|
||||
:- non_counted_backtracking setup_call_cleanup/3.
|
||||
|
||||
%% setup_call_cleanup(Setup, Goal, Cleanup).
|
||||
%
|
||||
% If Setup succeeds, Cleanup will be called after the execution of Goal. Goal itself can succeed or not.
|
||||
%
|
||||
% In this example, we use the predicate to always close an open file:
|
||||
%
|
||||
% ```
|
||||
% ?- setup_call_cleanup(open(File, read, Stream), do_something_with_stream(Stream), close(Stream)).
|
||||
% ```
|
||||
setup_call_cleanup(S, G, C) :-
|
||||
'$get_b_value'(B),
|
||||
'$call_with_inference_counting'(call(S)),
|
||||
'$set_cp_by_default'(B),
|
||||
'$get_current_block'(Bb),
|
||||
'$get_current_scc_block'(Bb),
|
||||
( C = _:CC,
|
||||
var(CC) ->
|
||||
instantiation_error(setup_call_cleanup/3)
|
||||
@@ -75,17 +152,16 @@ setup_call_cleanup(S, G, C) :-
|
||||
|
||||
scc_helper(C, G, Bb) :-
|
||||
'$get_cp'(Cp),
|
||||
'$install_scc_cleaner'(C, NBb),
|
||||
'$install_scc_cleaner'(C),
|
||||
'$call_with_inference_counting'(call(G)),
|
||||
( '$check_cp'(Cp) ->
|
||||
'$reset_block'(Bb),
|
||||
'$reset_scc_block'(Bb),
|
||||
run_cleaners_without_handling(Cp)
|
||||
; true
|
||||
; '$reset_block'(NBb),
|
||||
'$fail'
|
||||
; '$fail'
|
||||
).
|
||||
scc_helper(_, _, Bb) :-
|
||||
'$reset_block'(Bb),
|
||||
'$reset_scc_block'(Bb),
|
||||
'$push_ball_stack',
|
||||
run_cleaners_with_handling,
|
||||
'$pop_from_ball_stack',
|
||||
@@ -99,7 +175,7 @@ scc_helper(_, _, _) :-
|
||||
|
||||
run_cleaners_with_handling :-
|
||||
'$get_scc_cleaner'(C),
|
||||
'$get_level'(B),
|
||||
'$get_cp'(B),
|
||||
catch(C, _, true),
|
||||
'$set_cp_by_default'(B),
|
||||
run_cleaners_with_handling.
|
||||
@@ -110,7 +186,7 @@ run_cleaners_with_handling :-
|
||||
|
||||
run_cleaners_without_handling(Cp) :-
|
||||
'$get_scc_cleaner'(C),
|
||||
'$get_level'(B),
|
||||
'$get_cp'(B),
|
||||
call(C),
|
||||
'$set_cp_by_default'(B),
|
||||
run_cleaners_without_handling(Cp).
|
||||
@@ -144,6 +220,9 @@ handle_ile(B, _, _) :-
|
||||
|
||||
:- non_counted_backtracking call_with_inference_limit/3.
|
||||
|
||||
%% call_with_inference_limit(Goal, Limit, Result).
|
||||
%
|
||||
% Similar to `call(Goal)` but it limits the number of inferences for each solution of Goal.
|
||||
call_with_inference_limit(G, L, R) :-
|
||||
( integer(L) ->
|
||||
( L < 0 ->
|
||||
@@ -179,13 +258,17 @@ call_with_inference_limit(_, _, R, Bb, B) :-
|
||||
'$remove_inference_counter'(B, _),
|
||||
( '$get_ball'(Ball),
|
||||
'$push_ball_stack',
|
||||
'$get_level'(Cp),
|
||||
'$get_cp'(Cp),
|
||||
'$set_cp_by_default'(Cp)
|
||||
; '$remove_call_policy_check'(B),
|
||||
'$fail'
|
||||
),
|
||||
handle_ile(B, Ball, R).
|
||||
|
||||
%% partial_string(String, L, L0)
|
||||
%
|
||||
% Explicitly construct a partial string "manually". It can be used as an optimized append/3.
|
||||
% It's not recommended to use this predicate in application code.
|
||||
partial_string(String, L, L0) :-
|
||||
( String == [] ->
|
||||
L = L0
|
||||
@@ -195,9 +278,17 @@ partial_string(String, L, L0) :-
|
||||
'$create_partial_string'(Atom, L, L0)
|
||||
).
|
||||
|
||||
%% partial_string(+String)
|
||||
%
|
||||
% Succeeds if String is a _partial string_. A partial string is a string composed of several smaller
|
||||
% strings, even just one. That means all strings in Scryer are partial strings.
|
||||
partial_string(String) :-
|
||||
'$is_partial_string'(String).
|
||||
|
||||
%% partial_string_tail(+String, -Tail).
|
||||
%
|
||||
% Unifies Tail with the last section of the partial string.
|
||||
% It's not recommended to use this predicate in application code.
|
||||
partial_string_tail(String, Tail) :-
|
||||
( partial_string(String) ->
|
||||
'$partial_string_tail'(String, Tail)
|
||||
@@ -209,6 +300,9 @@ partial_string_tail(String, Tail) :-
|
||||
|
||||
:- meta_predicate(call_nth(0, ?)).
|
||||
|
||||
%% call_nth(Goal, N).
|
||||
%
|
||||
% Succeeds when Goal succeeded for the Nth time (there are at least N solutions)
|
||||
call_nth(Goal, N) :-
|
||||
can_be(integer, N),
|
||||
( integer(N) ->
|
||||
@@ -246,17 +340,55 @@ call_nth_nesting(C, ID) :-
|
||||
bb_put(ID, 0),
|
||||
bb_put(i_call_nth_counter, C).
|
||||
|
||||
%% countall(Goal, N).
|
||||
%
|
||||
% countall(Goal, N) counts all solutions of Goal and unifies N with
|
||||
% this number of solutions. This predicate always succeeds once.
|
||||
|
||||
:- meta_predicate(countall(0, ?)).
|
||||
|
||||
countall(Goal, N) :-
|
||||
can_be(integer, N),
|
||||
( integer(N) ->
|
||||
( N < 0 ->
|
||||
domain_error(not_less_than_zero, N, countall/2)
|
||||
; N > 0
|
||||
)
|
||||
; true
|
||||
),
|
||||
setup_call_cleanup(call_nth_nesting(C, ID),
|
||||
( ( Goal,
|
||||
bb_get(ID, N0),
|
||||
N1 is N0 + 1,
|
||||
bb_put(ID, N1),
|
||||
false
|
||||
; bb_get(ID, N)
|
||||
)
|
||||
),
|
||||
( bb_get(i_call_nth_counter, C) ->
|
||||
C1 is C - 1,
|
||||
bb_put(i_call_nth_counter, C1)
|
||||
; true
|
||||
)).
|
||||
|
||||
%% copy_term_nat(Source, Dest)
|
||||
%
|
||||
% Similar to `copy_term/2` but without attribute variables
|
||||
copy_term_nat(Source, Dest) :-
|
||||
'$copy_term_without_attr_vars'(Source, Dest).
|
||||
|
||||
|
||||
%% asserta(Module, Rule_Fact).
|
||||
%
|
||||
% Similar to `asserta/1` but allows specifying a Module
|
||||
asserta(Module, (Head :- Body)) :-
|
||||
!,
|
||||
'$asserta'(Module, Head, Body).
|
||||
asserta(Module, Fact) :-
|
||||
'$asserta'(Module, Fact, true).
|
||||
|
||||
%% assertz(Module, Rule_Fact).
|
||||
%
|
||||
% Similar to `assertz/1` but allows specifying a Module
|
||||
assertz(Module, (Head :- Body)) :-
|
||||
!,
|
||||
'$assertz'(Module, Head, Body).
|
||||
|
||||
+12
-9
@@ -50,11 +50,13 @@ programming based on call/N.
|
||||
Lambda expressions are represented by ordinary Prolog terms.
|
||||
There are two kinds of lambda expressions:
|
||||
|
||||
```
|
||||
Free+\X1^X2^ ..^XN^Goal
|
||||
|
||||
\X1^X2^ ..^XN^Goal
|
||||
```
|
||||
|
||||
The second is a shorthand for t+\X1^X2^..^XN^Goal.
|
||||
The second is a shorthand for `t+\X1^X2^..^XN^Goal`.
|
||||
|
||||
Xi are the parameters.
|
||||
|
||||
@@ -70,20 +72,20 @@ currently not checked. Violations may lead to unexpected bindings.
|
||||
|
||||
In the following example the parentheses around X>3 are necessary.
|
||||
|
||||
==
|
||||
```
|
||||
?- use_module(library(lambda)).
|
||||
?- use_module(library(lists)).
|
||||
|
||||
?- maplist(\X^(X>3),[4,5,9]).
|
||||
true.
|
||||
==
|
||||
```
|
||||
|
||||
In the following X is a variable that is shared by both instances of
|
||||
the lambda expression. The second query illustrates the cooperation of
|
||||
continuations and lambdas. The lambda expression is in this case a
|
||||
continuation expecting a further argument.
|
||||
|
||||
==
|
||||
```
|
||||
?- use_module(library(dif)).
|
||||
true.
|
||||
|
||||
@@ -92,11 +94,12 @@ continuation expecting a further argument.
|
||||
|
||||
?- Xs = [A,B], maplist(X+\dif(X), Xs).
|
||||
Xs = [A,B], dif:dif(X,A), dif:dif(X,B).
|
||||
==
|
||||
```
|
||||
|
||||
The following queries are all equivalent. To see this, use
|
||||
the fact f(x,y).
|
||||
==
|
||||
the fact `f(x,y)`.
|
||||
|
||||
```
|
||||
?- call(f,A1,A2).
|
||||
?- call(\X^f(X),A1,A2).
|
||||
?- call(\X^Y^f(X,Y), A1,A2).
|
||||
@@ -105,10 +108,10 @@ the fact f(x,y).
|
||||
?- call(f(A1),A2).
|
||||
?- f(A1,A2).
|
||||
A1 = x, A2 = y.
|
||||
==
|
||||
```
|
||||
|
||||
Further discussions
|
||||
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord
|
||||
[http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord](http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord)
|
||||
|
||||
@tbd Static expansion similar to apply_macros.
|
||||
@author Ulrich Neumerkel
|
||||
|
||||
+211
-49
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
List manipulation predicates
|
||||
*/
|
||||
|
||||
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5,
|
||||
memberchk/2, reverse/2, length/2, maplist/2,
|
||||
maplist/3, maplist/4, maplist/5, maplist/6,
|
||||
@@ -57,6 +61,20 @@
|
||||
resource_error(Resource, Context) :-
|
||||
throw(error(resource_error(Resource), Context)).
|
||||
|
||||
%% length(?Xs, ?N).
|
||||
%
|
||||
% Relates a list to its length (number of elements). It can be used to count the elements of a current list or
|
||||
% to create a list full of free variables with N length.
|
||||
%
|
||||
% ```
|
||||
% ?- length("abc", 3).
|
||||
% true.
|
||||
% ?- length("abc", N).
|
||||
% N = 3.
|
||||
% ?- length(Xs, 3).
|
||||
% Xs = [_A,_B,_C].
|
||||
% ```
|
||||
|
||||
length(Xs0, N) :-
|
||||
'$skip_max_list'(M, N, Xs0,Xs),
|
||||
!,
|
||||
@@ -74,7 +92,7 @@ length(_, N) :-
|
||||
|
||||
length_rundown(Xs, 0) :- !, Xs = [].
|
||||
length_rundown(Vs, N) :-
|
||||
\+ \+ '$project_atts':copy_term(Vs,Vs,[]), % unconstrained
|
||||
'$unattributed_var'(Vs), % unconstrained
|
||||
!,
|
||||
'$det_length_rundown'(Vs, N).
|
||||
length_rundown([_|Xs], N) :- % force unification
|
||||
@@ -82,7 +100,7 @@ length_rundown([_|Xs], N) :- % force unification
|
||||
length(Xs, N1). % maybe some new info on Xs
|
||||
|
||||
failingvarskip(Xs) :-
|
||||
\+ \+ '$project_atts':copy_term(Xs,Xs,[]), % unconstrained
|
||||
'$unattributed_var'(Xs), % unconstrained
|
||||
!.
|
||||
failingvarskip([_|Xs0]) :- % force unification
|
||||
'$skip_max_list'(_, _, Xs0,Xs),
|
||||
@@ -95,28 +113,71 @@ length_addendum([_|Xs], N, M) :-
|
||||
M1 is M + 1,
|
||||
length_addendum(Xs, N, M1).
|
||||
|
||||
%% member(?X, ?Xs).
|
||||
%
|
||||
% Succeeds when X unifies with an item of the list Xs, which can be at any position.
|
||||
%
|
||||
% ```
|
||||
% ?- member(X, "hello world").
|
||||
% X = h
|
||||
% ; ... .
|
||||
% ```
|
||||
|
||||
member(X, [X|_]).
|
||||
member(X, [_|Xs]) :- member(X, Xs).
|
||||
member(X, [L|Ls]) :-
|
||||
member_(Ls, L, X).
|
||||
|
||||
member_(_, X, X).
|
||||
member_([L|Ls], _, X) :-
|
||||
member_(Ls, L, X).
|
||||
|
||||
%% select(X, Xs0, Xs1).
|
||||
%
|
||||
% Succeeds when the list Xs1 is the list Xs0 without the item X
|
||||
%
|
||||
% ```
|
||||
% ?- select(c, "abcd", X).
|
||||
% X = "abd"
|
||||
% ; false.
|
||||
% ```
|
||||
select(X, [X|Xs], Xs).
|
||||
select(X, [Y|Xs], [Y|Ys]) :- select(X, Xs, Ys).
|
||||
|
||||
|
||||
%% append(+XsXs, ?Xs).
|
||||
%
|
||||
% Concatenates a list of lists
|
||||
%
|
||||
% ```
|
||||
% ?- append([[1, 2], [3]], Xs).
|
||||
% Xs = [1,2,3].
|
||||
% ```
|
||||
append([], []).
|
||||
append([L0|Ls0], Ls) :-
|
||||
append(L0, Rest, Ls),
|
||||
append(Ls0, Rest).
|
||||
|
||||
|
||||
%% append(Xs0, Xs1, Xs).
|
||||
%
|
||||
% List Xs is the concatenation of Xs0 and Xs1
|
||||
%
|
||||
% ```
|
||||
% ?- append([1,2,3], [4,5,6], Xs).
|
||||
% Xs = [1,2,3,4,5,6].
|
||||
% ```
|
||||
append([], R, R).
|
||||
append([X|L], R, [X|S]) :- append(L, R, S).
|
||||
|
||||
|
||||
%% memberchk(?X, +Xs).
|
||||
%
|
||||
% This predicate is similar to `member/2`, but it only provides a single answer
|
||||
memberchk(X, Xs) :- member(X, Xs), !.
|
||||
|
||||
|
||||
%% reverse(?Xs, ?Ys).
|
||||
%
|
||||
% Xs is the Ys list in reverse order
|
||||
%
|
||||
% ?- reverse([1,2,3], [3,2,1]).
|
||||
% true.
|
||||
%
|
||||
reverse(Xs, Ys) :-
|
||||
( nonvar(Xs) -> reverse(Xs, Ys, [], Xs)
|
||||
; reverse(Ys, Xs, [], Ys)
|
||||
@@ -126,81 +187,141 @@ reverse([], [], YsRev, YsRev).
|
||||
reverse([_|Xs], [Y1|Ys], YsPreludeRev, Xss) :-
|
||||
reverse(Xs, Ys, [Y1|YsPreludeRev], Xss).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the list Xs0
|
||||
%
|
||||
% ```
|
||||
% ?- maplist(write, [1,2,3]).
|
||||
% 123 true.
|
||||
% ```
|
||||
maplist(_, []).
|
||||
maplist(Cont1, [E1|E1s]) :-
|
||||
call(Cont1, E1),
|
||||
maplist(Cont1, E1s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0 and Xs1.
|
||||
%
|
||||
% ```
|
||||
% ?- maplist(length, ["hello", "prolog", "marseille"], Xs1).
|
||||
% Xs1 = [5,6,9].
|
||||
% ```
|
||||
maplist(_, [], []).
|
||||
maplist(Cont2, [E1|E1s], [E2|E2s]) :-
|
||||
call(Cont2, E1, E2),
|
||||
maplist(Cont2, E1s, E2s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1 and Xs2.
|
||||
maplist(_, [], [], []).
|
||||
maplist(Cont3, [E1|E1s], [E2|E2s], [E3|E3s]) :-
|
||||
call(Cont3, E1, E2, E3),
|
||||
maplist(Cont3, E1s, E2s, E3s).
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2 and Xs3.
|
||||
maplist(_, [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s]) :-
|
||||
call(Cont, E1, E2, E3, E4),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s).
|
||||
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3 and Xs4.
|
||||
maplist(_, [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s).
|
||||
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4 and Xs5.
|
||||
maplist(_, [], [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5, E6),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s).
|
||||
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5 and Xs6.
|
||||
maplist(_, [], [], [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5, E6, E7),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s).
|
||||
|
||||
|
||||
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6, ?Xs7).
|
||||
%
|
||||
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5, Xs6 and Xs7.
|
||||
maplist(_, [], [], [], [], [], [], [], []).
|
||||
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s], [E8|E8s]) :-
|
||||
call(Cont, E1, E2, E3, E4, E5, E6, E7, E8),
|
||||
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s, E8s).
|
||||
|
||||
|
||||
%% sum_list(+Xs, -Sum).
|
||||
%
|
||||
% Takes a lists of numbers and unifies Sum with the result of summing all the elements of the list.
|
||||
%
|
||||
% ```
|
||||
% ?- sum_list([2,2,2], 6).
|
||||
% true.
|
||||
% ```
|
||||
sum_list(Ls, S) :-
|
||||
foldl(lists:sum_, Ls, 0, S).
|
||||
|
||||
sum_(L, S0, S) :- S is S0 + L.
|
||||
|
||||
|
||||
|
||||
%% same_length(?Xs, ?Ys).
|
||||
%
|
||||
% Succeeds if Xs and Ys are lists of the same length
|
||||
same_length([], []).
|
||||
same_length([_|As], [_|Bs]) :-
|
||||
same_length(As, Bs).
|
||||
|
||||
%% foldl(+Predicate, ?Ls, +A0, ?A).
|
||||
%
|
||||
% foldl, sometimes called reduce, is a metapredicate that takes a predicate, a list of items
|
||||
% and a starting value, and outputs a single value. The predicate _Predicate_ must be able to take the current
|
||||
% element of the list, the previous value of the computation and the next value of the computation.
|
||||
%
|
||||
% For example, if we define sum_ as:
|
||||
%
|
||||
% ```
|
||||
% sum_(L, S0, S) :- S is S0 + L.
|
||||
% ```
|
||||
%
|
||||
% Then we can define `sum_list/2` as the following:
|
||||
%
|
||||
% ```
|
||||
% sum_list(Ls, S) :- foldl(sum_, Ls, 0, S).
|
||||
% ```
|
||||
|
||||
foldl(Goal_3, Ls, A0, A) :-
|
||||
foldl_(Ls, Goal_3, A0, A).
|
||||
|
||||
foldl_([], _, A, A).
|
||||
foldl_([L|Ls], G_3, A0, A) :-
|
||||
foldl(_, [], A, A).
|
||||
foldl(G_3, [L|Ls], A0, A) :-
|
||||
call(G_3, L, A0, A1),
|
||||
foldl_(Ls, G_3, A1, A).
|
||||
foldl(G_3, Ls, A1, A).
|
||||
|
||||
%% foldl(+Predicate, ?Ls0, ?Ls1, +A0, ?A).
|
||||
%
|
||||
% Same as `foldl/4` but with an extra list
|
||||
|
||||
foldl(Goal_4, Xs, Ys, A0, A) :-
|
||||
foldl_(Xs, Ys, Goal_4, A0, A).
|
||||
|
||||
|
||||
foldl_([], [], _, A, A).
|
||||
foldl_([X|Xs], [Y|Ys], G_4, A0, A) :-
|
||||
foldl(_, [], [], A, A).
|
||||
foldl(G_4, [X|Xs], [Y|Ys], A0, A) :-
|
||||
call(G_4, X, Y, A0, A1),
|
||||
foldl_(Xs, Ys, G_4, A1, A).
|
||||
foldl(G_4, Xs, Ys, A1, A).
|
||||
|
||||
%% transpose(?Ls, ?Ts).
|
||||
%
|
||||
% If Ls is a list of lists, Ts contains the transposition
|
||||
%
|
||||
% ```
|
||||
% ?- transpose([[1,1],[2,2]], Ts).
|
||||
% Ts = [[1,2],[1,2]].
|
||||
% ```
|
||||
transpose(Ls, Ts) :-
|
||||
lists_transpose(Ls, Ts).
|
||||
|
||||
@@ -214,7 +335,14 @@ transpose_(_, Fs, Lists0, Lists) :-
|
||||
|
||||
list_first_rest([L|Ls], L, Ls).
|
||||
|
||||
|
||||
%% list_to_set(+Ls0, -Set).
|
||||
%
|
||||
% Takes a list Ls0 and returns a list Set that doesn't contain any repeated element
|
||||
%
|
||||
% ```
|
||||
% ?- list_to_set([2,3,4,4,1,2], Set).
|
||||
% Set = [2,3,4,1].
|
||||
% ```
|
||||
list_to_set(Ls0, Ls) :-
|
||||
maplist(lists:with_var, Ls0, LVs0),
|
||||
keysort(LVs0, LVs),
|
||||
@@ -242,7 +370,14 @@ unify_same(E-V, Prev-Var, E-V) :-
|
||||
; true
|
||||
).
|
||||
|
||||
|
||||
%% nth0(?N, ?Ls, ?E).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from zero.
|
||||
%
|
||||
% ```
|
||||
% ?- nth0(2, [1,2,3,4], 3).
|
||||
% true.
|
||||
% ```
|
||||
nth0(N, Es0, E) :-
|
||||
nonvar(N),
|
||||
'$skip_max_list'(Skip, N, Es0,Es1),
|
||||
@@ -261,7 +396,6 @@ nth0(N, Es0, E) :-
|
||||
|
||||
skipn(N0, Es0,Es) :-
|
||||
N0>0,
|
||||
!, % should not be necessary #1028
|
||||
N1 is N0-1,
|
||||
Es0 = [_|Es1],
|
||||
skipn(N1, Es1,Es).
|
||||
@@ -277,6 +411,14 @@ nth0_el(N0,N, _,E, [E0|Es0]) :-
|
||||
N1 is N0+1,
|
||||
nth0_el(N1,N, E0,E, Es0).
|
||||
|
||||
%% nth1(?N, ?Ls, ?E).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from one.
|
||||
%
|
||||
% ```
|
||||
% ?- nth1(2, [1,2,3,4], 2).
|
||||
% true.
|
||||
% ```
|
||||
nth1(N, Es0, E) :-
|
||||
N \== 0,
|
||||
nth0(N, [_|Es0], E),
|
||||
@@ -284,13 +426,20 @@ nth1(N, Es0, E) :-
|
||||
|
||||
skipn(N0, Es0,Es, Xs0,Xs) :-
|
||||
N0>0,
|
||||
!, % should not be necessary #1028
|
||||
N1 is N0-1,
|
||||
Es0 = [E|Es1],
|
||||
Xs0 = [E|Xs1],
|
||||
skipn(N1, Es1,Es, Xs1,Xs).
|
||||
skipn(0, Es,Es, Xs,Xs).
|
||||
|
||||
%% nth0(?N, ?Ls, ?E, ?Rs).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from zero.
|
||||
%
|
||||
% ```
|
||||
% ?- nth0(2, [1,2,3,4], 3, [1,2,4]).
|
||||
% true.
|
||||
% ```
|
||||
nth0(N, Es0, E, Es) :-
|
||||
integer(N),
|
||||
N >= 0,
|
||||
@@ -315,45 +464,58 @@ nth0_elx(N0,N, E0,E, [E1|Es0], [E0|Es]) :-
|
||||
|
||||
% p.p.8.5
|
||||
|
||||
%% nth1(?N, ?Ls, ?E, ?Rs).
|
||||
%
|
||||
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from one.
|
||||
%
|
||||
% ```
|
||||
% ?- nth1(2, [1,2,3,4], 2, [1,3,4]).
|
||||
% true.
|
||||
% ```
|
||||
nth1(N, Es0, E, Es) :-
|
||||
N \== 0,
|
||||
nth0(N, [_|Es0], E, [_|Es]),
|
||||
N \== 0.
|
||||
|
||||
|
||||
%% list_max(+Xs, -Max).
|
||||
%
|
||||
% Takes a list Xs and unifies with the maximum value of the list
|
||||
list_max([N|Ns], Max) :-
|
||||
foldl(lists:list_max_, Ns, N, Max).
|
||||
|
||||
list_max_(N, Max0, Max) :-
|
||||
Max is max(N, Max0).
|
||||
|
||||
%% list_min(+Xs, -Min).
|
||||
%
|
||||
% Takes a list Xs and unifies with the minimum value of the list
|
||||
list_min([N|Ns], Min) :-
|
||||
foldl(lists:list_min_, Ns, N, Min).
|
||||
|
||||
list_min_(N, Min0, Min) :-
|
||||
Min is min(N, Min0).
|
||||
|
||||
%! permutation(?Xs, ?Ys) is nondet.
|
||||
%% permutation(?Xs, ?Ys) is nondet.
|
||||
%
|
||||
% True when Xs is a permutation of Ys. This can solve for Ys given
|
||||
% Xs or Xs given Ys, or even enumerate Xs and Ys together. The
|
||||
% predicate permutation/2 is primarily intended to generate
|
||||
% permutations. Note that a list of length N has N! permutations,
|
||||
% and unbounded permutation generation becomes prohibitively
|
||||
% expensive, even for rather short lists (10! = 3,628,800).
|
||||
% True when Xs is a permutation of Ys. This can solve for Ys given
|
||||
% Xs or Xs given Ys, or even enumerate Xs and Ys together. The
|
||||
% predicate `permutation/2` is primarily intended to generate
|
||||
% permutations. Note that a list of length N has N! permutations,
|
||||
% and unbounded permutation generation becomes prohibitively
|
||||
% expensive, even for rather short lists (10! = 3,628,800).
|
||||
%
|
||||
% The example below illustrates that Xs and Ys being proper lists
|
||||
% is not a sufficient condition to use the above replacement.
|
||||
% The example below illustrates that Xs and Ys being proper lists
|
||||
% is not a sufficient condition to use the above replacement.
|
||||
%
|
||||
% ==
|
||||
% ?- permutation([1,2], [X,Y]).
|
||||
% X = 1, Y = 2 ;
|
||||
% X = 2, Y = 1 ;
|
||||
% false.
|
||||
% ==
|
||||
% ```
|
||||
% ?- permutation([1,2], [X,Y]).
|
||||
% X = 1, Y = 2
|
||||
% ; X = 2, Y = 1
|
||||
% ; false.
|
||||
% ```
|
||||
%
|
||||
% @error type_error(list, Arg) if either argument is not a proper
|
||||
% or partial list.
|
||||
% Throws `type_error(list, Arg)` if either argument is not a proper
|
||||
% or partial list.
|
||||
|
||||
permutation(Xs, Ys) :-
|
||||
'$skip_max_list'(Xlen, _, Xs, XTail),
|
||||
|
||||
+97
-106
@@ -54,39 +54,38 @@
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
/** <module> Ordered set manipulation
|
||||
/** Ordered set manipulation
|
||||
|
||||
Ordered sets are lists with unique elements sorted to the standard order
|
||||
of terms (see sort/2). Exploiting ordering, many of the set operations
|
||||
of terms (see `sort/2`). Exploiting ordering, many of the set operations
|
||||
can be expressed in order N rather than N^2 when dealing with unordered
|
||||
sets that may contain duplicates. The library(ordsets) is available in a
|
||||
number of Prolog implementations. Our predicates are designed to be
|
||||
compatible with common practice in the Prolog community. The
|
||||
implementation is incomplete and relies partly on library(oset), an
|
||||
older ordered set library distributed with SWI-Prolog. New applications
|
||||
are advised to use library(ordsets).
|
||||
compatible with common practice in the Prolog community.
|
||||
Some of these predicates match directly to corresponding list
|
||||
operations. It is advised to use the versions from this library to make
|
||||
clear you are operating on ordered sets. An exception is member/2. See
|
||||
ord_memberchk/2.
|
||||
clear you are operating on ordered sets. An exception is `member/2`. See
|
||||
`ord_memberchk/2`.
|
||||
|
||||
The ordsets library is based on the standard order of terms. This
|
||||
implies it can handle all Prolog terms, including variables. Note
|
||||
however, that the ordering is not stable if a term inside the set is
|
||||
further instantiated. Also note that variable ordering changes if
|
||||
variables in the set are unified with each other or a variable in the
|
||||
set is unified with a variable that is `older' than the newest variable
|
||||
set is unified with a variable that is _older_ than the newest variable
|
||||
in the set. In practice, this implies that it is allowed to use
|
||||
member(X, OrdSet) on an ordered set that holds variables only if X is a
|
||||
fresh variable. In other cases one should cease using it as an ordset
|
||||
because the order it relies on may have been changed.
|
||||
*/
|
||||
|
||||
%! is_ordset(@Term) is semidet.
|
||||
%% is_ordset(@Term) is semidet.
|
||||
%
|
||||
% True if Term is an ordered set. All predicates in this library
|
||||
% expect ordered sets as input arguments. Failing to fullfil this
|
||||
% assumption results in undefined behaviour. Typically, ordered
|
||||
% sets are created by predicates from this library, sort/2 or
|
||||
% setof/3.
|
||||
% True if Term is an ordered set. All predicates in this library
|
||||
% expect ordered sets as input arguments. Failing to fullfil this
|
||||
% assumption results in undefined behaviour. Typically, ordered
|
||||
% sets are created by predicates from this library, `sort/2` or
|
||||
% `setof/3`.
|
||||
|
||||
is_ordset(Term) :-
|
||||
'$skip_max_list'(_, _, Term, Tail), Tail == [], %% is_list(Term),
|
||||
@@ -102,37 +101,35 @@ is_ordset3([H2|T], H) :-
|
||||
is_ordset3(T, H2).
|
||||
|
||||
|
||||
%! ord_empty(?List) is semidet.
|
||||
%% ord_empty(?List) is semidet.
|
||||
%
|
||||
% True when List is the empty ordered set. Simply unifies list
|
||||
% with the empty list. Not part of Quintus.
|
||||
% True when List is the empty ordered set. Simply unifies list
|
||||
% with the empty list. Not part of Quintus.
|
||||
|
||||
ord_empty([]).
|
||||
|
||||
|
||||
%! ord_seteq(+Set1, +Set2) is semidet.
|
||||
%% ord_seteq(+Set1, +Set2) is semidet.
|
||||
%
|
||||
% True if Set1 and Set2 have the same elements. As both are
|
||||
% canonical sorted lists, this is the same as ==/2.
|
||||
%
|
||||
% @compat sicstus
|
||||
% True if Set1 and Set2 have the same elements. As both are
|
||||
% canonical sorted lists, this is the same as `==/2`.
|
||||
|
||||
ord_seteq(Set1, Set2) :-
|
||||
Set1 == Set2.
|
||||
|
||||
|
||||
%! list_to_ord_set(+List, -OrdSet) is det.
|
||||
%% list_to_ord_set(+List, -OrdSet) is det.
|
||||
%
|
||||
% Transform a list into an ordered set. This is the same as
|
||||
% sorting the list.
|
||||
% Transform a list into an ordered set. This is the same as
|
||||
% sorting the list.
|
||||
|
||||
list_to_ord_set(List, Set) :-
|
||||
sort(List, Set).
|
||||
|
||||
|
||||
%! ord_intersect(+Set1, +Set2) is semidet.
|
||||
%% ord_intersect(+Set1, +Set2) is semidet.
|
||||
%
|
||||
% True if both ordered sets have a non-empty intersection.
|
||||
% True if both ordered sets have a non-empty intersection.
|
||||
|
||||
ord_intersect([H1|T1], L2) :-
|
||||
ord_intersect_(L2, H1, T1).
|
||||
@@ -148,31 +145,29 @@ ord_intersect__(>, H1, T1, _H2, T2) :-
|
||||
ord_intersect_(T2, H1, T1).
|
||||
|
||||
|
||||
%! ord_disjoint(+Set1, +Set2) is semidet.
|
||||
%% ord_disjoint(+Set1, +Set2) is semidet.
|
||||
%
|
||||
% True if Set1 and Set2 have no common elements. This is the
|
||||
% negation of ord_intersect/2.
|
||||
% True if Set1 and Set2 have no common elements. This is the
|
||||
% negation of `ord_intersect/2`.
|
||||
|
||||
ord_disjoint(Set1, Set2) :-
|
||||
\+ ord_intersect(Set1, Set2).
|
||||
|
||||
|
||||
%! ord_intersect(+Set1, +Set2, -Intersection)
|
||||
%% ord_intersect(+Set1, +Set2, -Intersection)
|
||||
%
|
||||
% Intersection holds the common elements of Set1 and Set2.
|
||||
% Intersection holds the common elements of Set1 and Set2.
|
||||
%
|
||||
% @deprecated Use ord_intersection/3
|
||||
% This predicate is *deprecated*. Use `ord_intersection/3`
|
||||
|
||||
ord_intersect(Set1, Set2, Intersection) :-
|
||||
oset_int(Set1, Set2, Intersection).
|
||||
|
||||
|
||||
%! ord_intersection(+PowerSet, -Intersection)
|
||||
%% ord_intersection(+PowerSet, -Intersection)
|
||||
%
|
||||
% Intersection of a powerset. True when Intersection is an ordered
|
||||
% set holding all elements common to all sets in PowerSet.
|
||||
%
|
||||
% @compat sicstus
|
||||
% Intersection of a powerset. True when Intersection is an ordered
|
||||
% set holding all elements common to all sets in PowerSet.
|
||||
|
||||
ord_intersection(PowerSet, Intersection) :-
|
||||
key_by_length(PowerSet, Pairs),
|
||||
@@ -190,10 +185,10 @@ l_int([_-H|T], S0, S) :-
|
||||
l_int(T, S1, S).
|
||||
|
||||
|
||||
%! ord_intersection(+Set1, +Set2, -Intersection) is det.
|
||||
%% ord_intersection(+Set1, +Set2, -Intersection) is det.
|
||||
%
|
||||
% Intersection holds the common elements of Set1 and Set2. Uses
|
||||
% ord_disjoint/2 if Intersection is bound to `[]` on entry.
|
||||
% Intersection holds the common elements of Set1 and Set2. Uses
|
||||
% `ord_disjoint/2` if Intersection is bound to `[]` on entry.
|
||||
|
||||
ord_intersection(Set1, Set2, Intersection) :-
|
||||
( Intersection == []
|
||||
@@ -202,13 +197,11 @@ ord_intersection(Set1, Set2, Intersection) :-
|
||||
).
|
||||
|
||||
|
||||
%! ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
|
||||
%% ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
|
||||
%
|
||||
% Intersection and difference between two ordered sets.
|
||||
% Intersection is the intersection between Set1 and Set2, while
|
||||
% Difference is defined by ord_subtract(Set2, Set1, Difference).
|
||||
%
|
||||
% @see ord_intersection/3 and ord_subtract/3.
|
||||
% Intersection and difference between two ordered sets.
|
||||
% Intersection is the intersection between Set1 and Set2, while
|
||||
% Difference is defined by `ord_subtract(Set2, Set1, Difference)`.
|
||||
|
||||
ord_intersection([], L, [], L) :- !.
|
||||
ord_intersection([_|_], [], [], []) :- !.
|
||||
@@ -224,35 +217,35 @@ ord_intersection2(>, H1, T1, H2, T2, Intersection, [H2|HDiff]) :-
|
||||
ord_intersection([H1|T1], T2, Intersection, HDiff).
|
||||
|
||||
|
||||
%! ord_add_element(+Set1, +Element, ?Set2) is det.
|
||||
%% ord_add_element(+Set1, +Element, ?Set2) is det.
|
||||
%
|
||||
% Insert an element into the set. This is the same as
|
||||
% ord_union(Set1, [Element], Set2).
|
||||
% Insert an element into the set. This is the same as
|
||||
% `ord_union(Set1, [Element], Set2)`.
|
||||
|
||||
ord_add_element(Set1, Element, Set2) :-
|
||||
oset_addel(Set1, Element, Set2).
|
||||
|
||||
|
||||
%! ord_del_element(+Set, +Element, -NewSet) is det.
|
||||
%% ord_del_element(+Set, +Element, -NewSet) is det.
|
||||
%
|
||||
% Delete an element from an ordered set. This is the same as
|
||||
% ord_subtract(Set, [Element], NewSet).
|
||||
% Delete an element from an ordered set. This is the same as
|
||||
% `ord_subtract(Set, [Element], NewSet)`.
|
||||
|
||||
ord_del_element(Set, Element, NewSet) :-
|
||||
oset_delel(Set, Element, NewSet).
|
||||
|
||||
|
||||
%! ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
|
||||
%% ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
|
||||
%
|
||||
% Selectchk/3, specialised for ordered sets. Is true when
|
||||
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
|
||||
% without duplicates. This implementation is only expected to work
|
||||
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
|
||||
% is meant to remind you of memberchk/2, which also expects its
|
||||
% first argument to be ground. ord_selectchk(X, S, T) =>
|
||||
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).
|
||||
% `selectchk/3`, specialised for ordered sets. Is true when
|
||||
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
|
||||
% without duplicates. This implementation is only expected to work
|
||||
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
|
||||
% is meant to remind you of `memberchk/2`, which also expects its
|
||||
% first argument to be ground. `ord_selectchk(X, S, T) =>
|
||||
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).`
|
||||
%
|
||||
% @author Richard O'Keefe
|
||||
% Author: Richard O'Keefe
|
||||
|
||||
ord_selectchk(Item, [X|Set1], [X|Set2]) :-
|
||||
X @< Item,
|
||||
@@ -266,19 +259,19 @@ ord_selectchk(Item, [Item|Set1], Set1) :-
|
||||
).
|
||||
|
||||
|
||||
%! ord_memberchk(+Element, +OrdSet) is semidet.
|
||||
%% ord_memberchk(+Element, +OrdSet) is semidet.
|
||||
%
|
||||
% True if Element is a member of OrdSet, compared using ==. Note
|
||||
% that _enumerating_ elements of an ordered set can be done using
|
||||
% member/2.
|
||||
% True if Element is a member of OrdSet, compared using ==. Note
|
||||
% that _enumerating_ elements of an ordered set can be done using
|
||||
% `member/2`.
|
||||
%
|
||||
% Some Prolog implementations also provide ord_member/2, with the
|
||||
% same semantics as ord_memberchk/2. We believe that having a
|
||||
% semidet ord_member/2 is unacceptably inconsistent with the *_chk
|
||||
% convention. Portable code should use ord_memberchk/2 or
|
||||
% member/2.
|
||||
% Some Prolog implementations also provide `ord_member/2`, with the
|
||||
% same semantics as `ord_memberchk/2`. We believe that having a
|
||||
% semidet `ord_member/2` is unacceptably inconsistent with the \*\_chk
|
||||
% convention. Portable code should use `ord_memberchk/2` or
|
||||
% `member/2`.
|
||||
%
|
||||
% @author Richard O'Keefe
|
||||
% Author: Richard O'Keefe
|
||||
|
||||
ord_memberchk(Item, [X1,X2,X3,X4|Xs]) :-
|
||||
!,
|
||||
@@ -303,9 +296,9 @@ ord_memberchk(Item, [X1]) :-
|
||||
Item == X1.
|
||||
|
||||
|
||||
%! ord_subset(+Sub, +Super) is semidet.
|
||||
%% ord_subset(+Sub, +Super) is semidet.
|
||||
%
|
||||
% Is true if all elements of Sub are in Super
|
||||
% Is true if all elements of Sub are in Super
|
||||
|
||||
ord_subset([], _).
|
||||
ord_subset([H1|T1], [H2|T2]) :-
|
||||
@@ -319,22 +312,20 @@ ord_subset_(=, _, T1, T2) :-
|
||||
ord_subset(T1, T2).
|
||||
|
||||
|
||||
%! ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
|
||||
%% ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
|
||||
%
|
||||
% Diff is the set holding all elements of InOSet that are not in
|
||||
% NotInOSet.
|
||||
% Diff is the set holding all elements of InOSet that are not in
|
||||
% NotInOSet.
|
||||
|
||||
ord_subtract(InOSet, NotInOSet, Diff) :-
|
||||
oset_diff(InOSet, NotInOSet, Diff).
|
||||
|
||||
|
||||
%! ord_union(+SetOfSets, -Union) is det.
|
||||
%% ord_union(+SetOfSets, -Union) is det.
|
||||
%
|
||||
% True if Union is the union of all elements in the superset
|
||||
% SetOfSets. Each member of SetOfSets must be an ordered set, the
|
||||
% sets need not be ordered in any way.
|
||||
%
|
||||
% @author Copied from YAP, probably originally by Richard O'Keefe.
|
||||
% True if Union is the union of all elements in the superset
|
||||
% SetOfSets. Each member of SetOfSets must be an ordered set, the
|
||||
% sets need not be ordered in any way.
|
||||
|
||||
ord_union([], []).
|
||||
ord_union([Set|Sets], Union) :-
|
||||
@@ -355,18 +346,18 @@ ord_union_all(N, Sets0, Union, Sets) :-
|
||||
).
|
||||
|
||||
|
||||
%! ord_union(+Set1, +Set2, ?Union) is det.
|
||||
%% ord_union(+Set1, +Set2, ?Union) is det.
|
||||
%
|
||||
% Union is the union of Set1 and Set2
|
||||
% Union is the union of Set1 and Set2
|
||||
|
||||
ord_union(Set1, Set2, Union) :-
|
||||
oset_union(Set1, Set2, Union).
|
||||
|
||||
|
||||
%! ord_union(+Set1, +Set2, -Union, -New) is det.
|
||||
%% ord_union(+Set1, +Set2, -Union, -New) is det.
|
||||
%
|
||||
% True iff ord_union(Set1, Set2, Union) and
|
||||
% ord_subtract(Set2, Set1, New).
|
||||
% True iff `ord_union(Set1, Set2, Union)` and
|
||||
% `ord_subtract(Set2, Set1, New)`.
|
||||
|
||||
ord_union([], Set2, Set2, Set2).
|
||||
ord_union([H|T], Set2, Union, New) :-
|
||||
@@ -390,26 +381,26 @@ ord_union_2([H|T], H2, T2, Union, New) :-
|
||||
ord_union(Order, H, T, H2, T2, Union, New).
|
||||
|
||||
|
||||
%! ord_symdiff(+Set1, +Set2, ?Difference) is det.
|
||||
%% ord_symdiff(+Set1, +Set2, ?Difference) is det.
|
||||
%
|
||||
% Is true when Difference is the symmetric difference of Set1 and
|
||||
% Set2. I.e., Difference contains all elements that are not in the
|
||||
% intersection of Set1 and Set2. The semantics is the same as the
|
||||
% sequence below (but the actual implementation requires only a
|
||||
% single scan).
|
||||
% Is true when Difference is the symmetric difference of Set1 and
|
||||
% Set2. I.e., Difference contains all elements that are not in the
|
||||
% intersection of Set1 and Set2. The semantics is the same as the
|
||||
% sequence below (but the actual implementation requires only a
|
||||
% single scan).
|
||||
%
|
||||
% ==
|
||||
% ord_union(Set1, Set2, Union),
|
||||
% ord_intersection(Set1, Set2, Intersection),
|
||||
% ord_subtract(Union, Intersection, Difference).
|
||||
% ==
|
||||
% ```
|
||||
% ord_union(Set1, Set2, Union),
|
||||
% ord_intersection(Set1, Set2, Intersection),
|
||||
% ord_subtract(Union, Intersection, Difference).
|
||||
% ```
|
||||
%
|
||||
% For example:
|
||||
% For example:
|
||||
%
|
||||
% ==
|
||||
% ?- ord_symdiff([1,2], [2,3], X).
|
||||
% X = [1,3].
|
||||
% ==
|
||||
% ```
|
||||
% ?- ord_symdiff([1,2], [2,3], X).
|
||||
% X = [1,3].
|
||||
% ```
|
||||
|
||||
ord_symdiff([], Set2, Set2).
|
||||
ord_symdiff([H1|T1], Set2, Difference) :-
|
||||
@@ -457,7 +448,7 @@ ord_symdiff(>, H1, T1, H2, Set2, [H2|Difference]) :-
|
||||
*/
|
||||
|
||||
|
||||
/** <module> Ordered set manipulation
|
||||
/* Ordered set manipulation
|
||||
|
||||
This library defines set operations on sets represented as ordered
|
||||
lists.
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
Public domain code.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Predicates for reasoning about the operating system (OS) environment.
|
||||
|
||||
This includes predicates about environment variables, calls to shell and
|
||||
finding out the PID of the running system.
|
||||
*/
|
||||
|
||||
:- module(os, [getenv/2,
|
||||
setenv/2,
|
||||
unsetenv/1,
|
||||
@@ -24,25 +30,60 @@
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(si)).
|
||||
|
||||
%% getenv(+Key, -Value).
|
||||
%
|
||||
% True iff Value contains the value of the environment variable Key.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- getenv("LANG", Ls).
|
||||
% Ls = "en_US.UTF-8".
|
||||
% ```
|
||||
getenv(Key, Value) :-
|
||||
must_be_env_var(Key),
|
||||
'$getenv'(Key, Value).
|
||||
|
||||
%% setenv(+Key, +Value).
|
||||
%
|
||||
% Sets the environment variable Key to Value
|
||||
setenv(Key, Value) :-
|
||||
must_be_env_var(Key),
|
||||
must_be_chars(Value),
|
||||
'$setenv'(Key, Value).
|
||||
|
||||
%% unsetenv(+Key).
|
||||
%
|
||||
% Unsets the environment variable Key
|
||||
unsetenv(Key) :-
|
||||
must_be_env_var(Key),
|
||||
'$unsetenv'(Key).
|
||||
|
||||
%% shell(+Command)
|
||||
%
|
||||
% Equivalent to `shell(Command, 0)`.
|
||||
shell(Command) :- shell(Command, 0).
|
||||
|
||||
%% shell(+Command, -Status).
|
||||
%
|
||||
% True iff executes Command in a shell of the operating system and the exit code is Status.
|
||||
% Keep in mind the shell syntax is dependant on the operating system, so it should be
|
||||
% used very carefully.
|
||||
%
|
||||
% Example (using Linux and fish shell):
|
||||
%
|
||||
% ```
|
||||
% ?- shell("echo $SHELL", Status).
|
||||
% /bin/fish
|
||||
% Status = 0.
|
||||
% ```
|
||||
shell(Command, Status) :-
|
||||
must_be_chars(Command),
|
||||
can_be(integer, Status),
|
||||
'$shell'(Command, Status).
|
||||
|
||||
%% pid(-PID).
|
||||
%
|
||||
% True iff PID is the process identification number of current Scryer Prolog instance.
|
||||
pid(PID) :-
|
||||
can_be(integer, PID),
|
||||
'$pid'(PID).
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/** Reasoning about pairs.
|
||||
|
||||
Pairs are Prolog terms with principal functor `(-)/2`. A pair
|
||||
often has the form `Key-Value`. The predicates of this library
|
||||
relate pairs to keys and values.
|
||||
*/
|
||||
|
||||
:- module(pairs, [pairs_keys_values/3,
|
||||
pairs_keys/2,
|
||||
pairs_values/2,
|
||||
@@ -7,12 +14,25 @@
|
||||
|
||||
:- meta_predicate map_list_to_pairs(2, ?, ?).
|
||||
|
||||
%% pairs_keys_values(?Pairs, ?Keys, ?Values)
|
||||
%
|
||||
% The first argument is a list of Pairs, the second the corresponding
|
||||
% Keys, and the third argument the corresponding values.
|
||||
|
||||
pairs_keys_values([], [], []).
|
||||
pairs_keys_values([A-B|ABs], [A|As], [B|Bs]) :-
|
||||
pairs_keys_values(ABs, As, Bs).
|
||||
|
||||
%% pairs_keys(?Pairs, ?Keys)
|
||||
%
|
||||
% Same as `pairs_keys_values(Pairs, Keys, _)`.
|
||||
|
||||
pairs_keys(Ps, Ks) :- pairs_keys_values(Ps, Ks, _).
|
||||
|
||||
%% pairs_values(?Pairs, ?Values)
|
||||
%
|
||||
% Same as `pairs_keys_values(Pairs, _, Values)`.
|
||||
|
||||
pairs_values(Ps, Vs) :- pairs_keys_values(Ps, _, Vs).
|
||||
|
||||
map_list_to_pairs(Pred, Ls, Ps) :-
|
||||
|
||||
+36
-33
@@ -1,13 +1,11 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Pure I/O
|
||||
========
|
||||
/** Pure I/O.
|
||||
|
||||
Our goal is to encourage the use of definite clause grammars (DCGs)
|
||||
for describing strings. The predicates phrase_from_file/[2,3],
|
||||
phrase_to_file/[2,3] and phrase_to_stream/2 let us apply DCGs
|
||||
for describing strings. The predicates `phrase_from_file/[2,3]`,
|
||||
`phrase_to_file/[2,3]` and `phrase_to_stream/2` let us apply DCGs
|
||||
transparently to files and streams, and therefore decouple side-effects
|
||||
from declarative descriptions.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
*/
|
||||
|
||||
:- module(pio, [phrase_from_file/2,
|
||||
phrase_from_file/3,
|
||||
@@ -29,16 +27,18 @@
|
||||
:- meta_predicate(phrase_to_file(2, ?, ?)).
|
||||
:- meta_predicate(phrase_to_stream(2, ?)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
phrase_from_file(GRBody, File)
|
||||
|
||||
True if grammar rule body GRBody covers the contents of File,
|
||||
represented as a list of characters.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% phrase_from_file(+GRBody, +File)
|
||||
%
|
||||
% True if grammar rule body GRBody covers the contents of File,
|
||||
% represented as a list of characters.
|
||||
|
||||
phrase_from_file(NT, File) :-
|
||||
phrase_from_file(NT, File, []).
|
||||
|
||||
%% phrase_from_file(+GRBody, +File, +Options)
|
||||
%
|
||||
% Like `phrase_from_file/2`, using Options to open the file.
|
||||
|
||||
phrase_from_file(NT, File, Options) :-
|
||||
( var(File) -> instantiation_error(phrase_from_file/3)
|
||||
; must_be(list, Options),
|
||||
@@ -68,23 +68,22 @@ reader_step(Stream, Pos, Xs0) :-
|
||||
stream_to_lazy_list(Stream, Xs)
|
||||
).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
phrase_to_stream(+GRBody, +Stream)
|
||||
|
||||
Emit the list of characters described by the grammar rule body
|
||||
GRBody to Stream.
|
||||
|
||||
An ideal implementation of phrase_to_stream/2 writes each character
|
||||
as soon as it becomes known and no choice-points remain, and thus
|
||||
avoids the manifestation of the entire string in memory. See #691
|
||||
for more information.
|
||||
|
||||
The current preliminary implementation is provided so that Prolog
|
||||
programmers can already get used to describing output with DCGs,
|
||||
and then writing it to a file when necessary. This simple
|
||||
implementation suffices as long as the entire contents can be
|
||||
represented in memory, and thus covers a large number of use cases.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% phrase_to_stream(+GRBody, +Stream)
|
||||
%
|
||||
% Emit the list of characters described by the grammar rule body
|
||||
% GRBody to Stream.
|
||||
%
|
||||
% An ideal implementation of `phrase_to_stream/2` writes each
|
||||
% character as soon as it becomes known and no choice-points remain,
|
||||
% and thus avoids the manifestation of the entire string in memory.
|
||||
% See [#691](https://github.com/mthom/scryer-prolog/issues/691) for
|
||||
% more information.
|
||||
%
|
||||
% The current preliminary implementation is provided so that Prolog
|
||||
% programmers can already get used to describing output with DCGs,
|
||||
% and then writing it to a file when necessary. This simple
|
||||
% implementation suffices as long as the entire contents can be
|
||||
% represented in memory, and thus covers a large number of use cases.
|
||||
|
||||
phrase_to_stream(GRBody, Stream) :-
|
||||
phrase(GRBody, Cs),
|
||||
@@ -101,14 +100,18 @@ phrase_to_stream(GRBody, Stream) :-
|
||||
% maplist(put_char(Stream), Cs). It also works for binary streams.
|
||||
'$put_chars'(Stream, Cs).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
phrase_to_file(+GRBody, +File), writing the string described
|
||||
by GRBody to File.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% phrase_to_file(+GRBody, +File)
|
||||
%
|
||||
% Write the string described by GRBody to File.
|
||||
|
||||
phrase_to_file(GRBody, File) :-
|
||||
phrase_to_file(GRBody, File, []).
|
||||
|
||||
|
||||
%% phrase_to_file(+GRBody, +File, +Options)
|
||||
%
|
||||
% Like `phrase_to_file/2`, using Options to open the file.
|
||||
|
||||
phrase_to_file(GRBody, File, Options) :-
|
||||
setup_call_cleanup(open(File, write, Stream, Options),
|
||||
phrase_to_stream(GRBody, Stream),
|
||||
|
||||
+25
-7
@@ -1,24 +1,38 @@
|
||||
:- module(random, [maybe/0, random/1, random_integer/3, set_random/1]).
|
||||
/**
|
||||
This library provides probabilistic predicates and random number generators.
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
To retain desirable declarative properties, predicates that internally
|
||||
use random numbers should be equipped with an argument that specifies
|
||||
the random seed. This makes everything completely reproducible.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
To retain desirable declarative properties, predicates that internally
|
||||
use random numbers should be equipped with an argument that specifies
|
||||
the random seed. This makes everything completely reproducible.
|
||||
*/
|
||||
|
||||
:- module(random, [maybe/0, random/1, random_integer/3, set_random/1]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
% succeeds with probability 0.5.
|
||||
%% maybe.
|
||||
%
|
||||
% Succeeds with probability 0.5.
|
||||
maybe :- '$maybe'.
|
||||
|
||||
% The higher the precision, the slower it gets.
|
||||
random_number_precision(64).
|
||||
|
||||
%% random(-R).
|
||||
%
|
||||
% Generates a random floating number between 0 (inclusive) and 1 (exclusive).
|
||||
random(R) :-
|
||||
var(R),
|
||||
random_number_precision(N),
|
||||
rnd(N, R).
|
||||
|
||||
%% random_integer(+Lower, +Upper, -R).
|
||||
%
|
||||
% Generates a random integer number between Lower (inclusive) and Upper (exclusive).
|
||||
%
|
||||
% Throws `instantiation_error` if Lower or Upper are variables.
|
||||
%
|
||||
% Throws `type_error` if Lower or Upper aren't integers.
|
||||
random_integer(Lower, Upper, R) :-
|
||||
var(R),
|
||||
( (var(Lower) ; var(Upper)) ->
|
||||
@@ -46,6 +60,10 @@ rnd_(N, R0, R) :-
|
||||
R1 is R0 + 1.0 / 2.0 ^ N,
|
||||
rnd_(N1, R1, R).
|
||||
|
||||
%% set_random(+Seed).
|
||||
%
|
||||
% Sets a seed that will be used for subsequent random generations in this library.
|
||||
% It's necessary to set a seed to provide reproducible executions using this library.
|
||||
set_random(Seed) :-
|
||||
( nonvar(Seed) ->
|
||||
( Seed = seed(S) ->
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
/** Predicates from [*Indexing dif/2*](https://arxiv.org/abs/1607.01590).
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
?- tfilter(=(a), [X,Y], Es).
|
||||
X = a, Y = a, Es = "aa"
|
||||
; X = a, Es = "a", dif:dif(a,Y)
|
||||
; Y = a, Es = "a", dif:dif(a,X)
|
||||
; Es = [], dif:dif(a,X), dif:dif(a,Y).
|
||||
```
|
||||
*/
|
||||
|
||||
:- module(reif, [if_/3, (=)/3, (',')/3, (;)/3, cond_t/3, dif/3,
|
||||
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
|
||||
tpartition/4]).
|
||||
|
||||
+62
-49
@@ -2,57 +2,70 @@
|
||||
Predicates for parsing HTML and XML documents.
|
||||
Written 2020-2022 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
Currently, two predicates are provided:
|
||||
|
||||
- load_html(+Source, -Es, +Options)
|
||||
- load_xml(+Source, -Es, +Options)
|
||||
|
||||
These predicates parse HTML and XML documents, respectively.
|
||||
|
||||
Source must be one of:
|
||||
|
||||
- a list of characters with the document contents
|
||||
- stream(S), specifying a stream S from which to read the content
|
||||
- file(Name), where Name is a list of characters specifying a file name.
|
||||
|
||||
Es is unified with the abstract syntax tree of the parsed document,
|
||||
represented as a list of elements where each is of the form:
|
||||
|
||||
* a list of characters, representing text
|
||||
* element(Name, Attrs, Children)
|
||||
- Name, an atom, is the name of the tag
|
||||
- Attrs is a list of Key=Value pairs:
|
||||
Key is an atom, and Value is a list of characters
|
||||
- Children is a list of elements as specified here.
|
||||
|
||||
Currently, Options are ignored. In the future, more options may be
|
||||
provided to control parsing.
|
||||
|
||||
Example:
|
||||
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
|
||||
|
||||
Yielding:
|
||||
|
||||
Es = [element(html,[],
|
||||
[element(head,[],
|
||||
[element(title,[],
|
||||
["Hello!"])]),
|
||||
element(body,[],[])])].
|
||||
|
||||
library(xpath) provides convenient reasoning about parsed documents.
|
||||
For example, to fetch the title of the document above, we can use:
|
||||
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
|
||||
xpath(Es, //title(text), T).
|
||||
|
||||
Yielding T = "Hello!".
|
||||
|
||||
Use http_open/3 from library(http/http_open) to read answers from
|
||||
web servers via streams.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Predicates for parsing HTML and XML documents.
|
||||
|
||||
Currently, two predicates are provided:
|
||||
|
||||
- `load_html(+Source, -Es, +Options)`
|
||||
- `load_xml(+Source, -Es, +Options)`
|
||||
|
||||
These predicates parse HTML and XML documents, respectively.
|
||||
|
||||
Source must be one of:
|
||||
|
||||
- a list of characters with the document contents
|
||||
- `stream(S)`, specifying a stream S from which to read the content
|
||||
- `file(Name)`, where Name is a list of characters specifying a file name.
|
||||
|
||||
Es is unified with the abstract syntax tree of the parsed document,
|
||||
represented as a list of elements where each is of the form:
|
||||
|
||||
* a list of characters, representing text
|
||||
|
||||
* `element(Name, Attrs, Children)`
|
||||
|
||||
- `Name`, an atom, is the name of the tag
|
||||
|
||||
- `Attrs` is a list of `Key=Value` pairs:
|
||||
`Key` is an atom, and `Value` is a list of characters
|
||||
|
||||
- `Children` is a list of elements as specified here.
|
||||
|
||||
Currently, Options are ignored. In the future, more options may be
|
||||
provided to control parsing.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
|
||||
```
|
||||
|
||||
Yielding:
|
||||
|
||||
```
|
||||
Es = [element(html,[],
|
||||
[element(head,[],
|
||||
[element(title,[],
|
||||
["Hello!"])]),
|
||||
element(body,[],[])])].
|
||||
```
|
||||
|
||||
`library(xpath)` provides convenient reasoning about parsed documents.
|
||||
For example, to fetch the title of the document above, we can use:
|
||||
|
||||
```
|
||||
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
|
||||
xpath(Es, //title(text), T).
|
||||
```
|
||||
|
||||
Yielding `T = "Hello!"`.
|
||||
|
||||
Use `http_open/3` from `library(http/http_open)` to read answers from
|
||||
web servers via streams.
|
||||
*/
|
||||
|
||||
:- module(sgml, [load_html/3,
|
||||
load_xml/3]).
|
||||
|
||||
|
||||
+30
-15
@@ -1,34 +1,43 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
Safe type tests
|
||||
===============
|
||||
/** Safe type tests.
|
||||
|
||||
"si" stands for "sufficiently instantiated".
|
||||
"si" stands for "sufficiently instantiated". It can also be read as
|
||||
"safe inference", so possibly also other predicates are candidates
|
||||
for this library.
|
||||
|
||||
These predicates:
|
||||
A safe type test:
|
||||
|
||||
- throw instantiation errors if the argument is
|
||||
- throws an *instantiation error* if the argument is
|
||||
not sufficiently instantiated to make a sound decision
|
||||
- succeed if the argument is of the specified type
|
||||
- fail otherwise.
|
||||
- *succeeds* if the argument is of the specified type
|
||||
- *fails* otherwise.
|
||||
|
||||
For instance, atom_si(A) yields an *instantiation error* if A is a
|
||||
For instance, `atom_si(A)` yields an *instantiation error* if `A` is a
|
||||
variable. This is logically sound, since in that case the argument
|
||||
is not sufficiently instantiated to make any decision.
|
||||
|
||||
The definitions are taken from:
|
||||
The definitions are taken from [Safer type tests in Prolog](https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog).
|
||||
|
||||
https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog
|
||||
Examples:
|
||||
|
||||
"si" can also be read as "safe inference", so possibly also other
|
||||
predicates are candidates for this library.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
```
|
||||
?- chars_si(Cs).
|
||||
error(instantiation_error,list_si/1).
|
||||
?- chars_si([h|Cs]).
|
||||
error(instantiation_error,list_si/1).
|
||||
?- chars_si("hello").
|
||||
true.
|
||||
?- chars_si(hello).
|
||||
false.
|
||||
```
|
||||
*/
|
||||
|
||||
:- module(si, [atom_si/1,
|
||||
integer_si/1,
|
||||
atomic_si/1,
|
||||
list_si/1,
|
||||
chars_si/1]).
|
||||
chars_si/1,
|
||||
dif_si/2]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
@@ -56,3 +65,9 @@ list_si(L0) :-
|
||||
chars_si(Cs) :-
|
||||
list_si(Cs),
|
||||
'$is_partial_string'(Cs).
|
||||
|
||||
dif_si(X, Y) :-
|
||||
X \== Y,
|
||||
( X \= Y -> true
|
||||
; throw(error(instantiation_error,dif_si/2))
|
||||
).
|
||||
|
||||
+20
-20
@@ -77,9 +77,9 @@ thesis project, for example.
|
||||
A *linear programming problem* or simply *linear program* (LP)
|
||||
consists of:
|
||||
|
||||
- a set of _linear_ **constraints**
|
||||
- a set of **variables**
|
||||
- a _linear_ **objective function**.
|
||||
- a set of _linear_ *constraints*
|
||||
- a set of *variables*
|
||||
- a _linear_ *objective function*.
|
||||
|
||||
The goal is to assign values to the variables so as to _maximize_ (or
|
||||
minimize) the value of the objective function while satisfying all
|
||||
@@ -107,10 +107,10 @@ non-negativity constraints should therefore be stated explicitly.
|
||||
This is the "radiation therapy" example, taken from _Introduction to
|
||||
Operations Research_ by Hillier and Lieberman.
|
||||
|
||||
[**Prolog DCG notation**](https://www.metalevel.at/prolog/dcg) is
|
||||
[*Prolog DCG notation*](https://www.metalevel.at/prolog/dcg) is
|
||||
used to _implicitly_ thread the state through posting the constraints:
|
||||
|
||||
==
|
||||
```
|
||||
:- use_module(library(simplex)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
@@ -125,15 +125,15 @@ post_constraints -->
|
||||
constraint([0.6*x1, 0.4*x2] >= 6),
|
||||
constraint([x1] >= 0),
|
||||
constraint([x2] >= 0).
|
||||
==
|
||||
```
|
||||
|
||||
An example query:
|
||||
|
||||
==
|
||||
```
|
||||
?- radiation(S), variable_value(S, x1, Val1),
|
||||
variable_value(S, x2, Val2).
|
||||
S = solved(...), Val1 = 15 rdiv 2, Val2 = 9 rdiv 2.
|
||||
==
|
||||
```
|
||||
|
||||
## Example 2 {#simplex-ex-2}
|
||||
|
||||
@@ -143,7 +143,7 @@ Here is an instance of the knapsack problem described above, where `C
|
||||
variables, `x(1)` and `x(2)` that denote how many items to take of
|
||||
each type.
|
||||
|
||||
==
|
||||
```
|
||||
:- use_module(library(simplex)).
|
||||
|
||||
knapsack(S) :-
|
||||
@@ -155,15 +155,15 @@ knapsack_constraints(S) :-
|
||||
constraint([6*x(1), 4*x(2)] =< 8, S0, S1),
|
||||
constraint([x(1)] =< 1, S1, S2),
|
||||
constraint([x(2)] =< 2, S2, S).
|
||||
==
|
||||
```
|
||||
|
||||
An example query yields:
|
||||
|
||||
==
|
||||
```
|
||||
?- knapsack(S), variable_value(S, x(1), X1),
|
||||
variable_value(S, x(2), X2).
|
||||
S = solved(...), X1 = 1 rdiv 1, X2 = 1 rdiv 2.
|
||||
==
|
||||
```
|
||||
|
||||
That is, we are to take the one item of the first type, and half of one of
|
||||
the items of the other type to maximize the total value of items in the
|
||||
@@ -171,23 +171,23 @@ knapsack.
|
||||
|
||||
If items can not be split, integrality constraints have to be imposed:
|
||||
|
||||
==
|
||||
```
|
||||
knapsack_integral(S) :-
|
||||
knapsack_constraints(S0),
|
||||
constraint(integral(x(1)), S0, S1),
|
||||
constraint(integral(x(2)), S1, S2),
|
||||
maximize([7*x(1), 4*x(2)], S2, S).
|
||||
==
|
||||
```
|
||||
|
||||
Now the result is different:
|
||||
|
||||
==
|
||||
```
|
||||
?- knapsack_integral(S), variable_value(S, x(1), X1),
|
||||
variable_value(S, x(2), X2).
|
||||
|
||||
X1 = 0
|
||||
X2 = 2
|
||||
==
|
||||
```
|
||||
|
||||
That is, we are to take only the _two_ items of the second type.
|
||||
Notice in particular that always choosing the remaining item with best
|
||||
@@ -207,7 +207,7 @@ The task is to find a _minimal_ number of these coins that amount to
|
||||
111 units in total. We introduce variables `c(1)`, `c(5)` and `c(20)`
|
||||
denoting how many coins to take of the respective type:
|
||||
|
||||
==
|
||||
```
|
||||
:- use_module(library(simplex)).
|
||||
|
||||
coins(S) :-
|
||||
@@ -226,16 +226,16 @@ coins -->
|
||||
constraint(integral(c(5))),
|
||||
constraint(integral(c(20))),
|
||||
minimize([c(1), c(5), c(20)]).
|
||||
==
|
||||
```
|
||||
|
||||
An example query:
|
||||
|
||||
==
|
||||
```
|
||||
?- coins(S), variable_value(S, c(1), C1),
|
||||
variable_value(S, c(5), C5),
|
||||
variable_value(S, c(20), C20).
|
||||
S = solved(...), C1 = 1 rdiv 1, C5 = 2 rdiv 1, C20 = 5 rdiv 1.
|
||||
==
|
||||
```
|
||||
|
||||
@author [Markus Triska](https://www.metalevel.at)
|
||||
*/
|
||||
|
||||
+42
-5
@@ -1,4 +1,9 @@
|
||||
|
||||
/**
|
||||
Predicates for handling network sockets, both as a server and as a client.
|
||||
As a server, you should open a socket an call `socket_server_accept/4` to get a stream for each connection.
|
||||
As a client, you should just open a socket and you will receive a stream.
|
||||
In both cases, with a stream, you can use the usual predicates to read and write to the stream.
|
||||
*/
|
||||
:- module(sockets, [socket_client_open/3,
|
||||
socket_server_open/2,
|
||||
socket_server_accept/4,
|
||||
@@ -7,6 +12,18 @@
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% socket_client_open(+Addr, -Stream, +Options).
|
||||
%
|
||||
% Open a socket to a server, returning a stream. Addr must satisfy `Addr = Address:Port`.
|
||||
%
|
||||
% The following options are available:
|
||||
%
|
||||
% * `alias(+Alias)`: Set an alias to the stream
|
||||
% * `eof_action(+Action)`: Defined what happens if the end of the stream is reached. Values: `error`, `eof_code` and `reset`.
|
||||
% * `reposition(+Boolean)`: Specifies whether repositioning is required for the stream. `false` is the default.
|
||||
% * `type(+Type)`: Type can be `text` or `binary`. Defines the type of the stream, if it's optimized for plain text
|
||||
% or just binary
|
||||
%
|
||||
socket_client_open(Addr, Stream, Options) :-
|
||||
( var(Addr) ->
|
||||
throw(error(instantiation_error, socket_client_open/3))
|
||||
@@ -27,7 +44,11 @@ socket_client_open(Addr, Stream, Options) :-
|
||||
socket_client_open/3),
|
||||
'$socket_client_open'(Address, Port, Stream, Alias, EOFAction, Reposition, Type).
|
||||
|
||||
|
||||
%% socket_server_open(+Addr, -ServerSocket).
|
||||
%
|
||||
% Open a server socket, returning a ServerSocket. Use that ServerSocket to accept incoming connections in
|
||||
% `socket_server_accept/4`. Addr must satisfy `Addr = Address:Port`. Depending on the operating system
|
||||
% configuration, some ports might be reserved for superusers.
|
||||
socket_server_open(Addr, ServerSocket) :-
|
||||
must_be(var, ServerSocket),
|
||||
( ( integer(Addr) ; var(Addr) ) ->
|
||||
@@ -39,7 +60,19 @@ socket_server_open(Addr, ServerSocket) :-
|
||||
'$socket_server_open'(Address, Port, ServerSocket)
|
||||
).
|
||||
|
||||
|
||||
%% socket_server_accept(+ServerSocket, -Client, -Stream, +Options).
|
||||
%
|
||||
% Given a ServerSocket and a list of Options, accepts a incoming connection, returning data from the Client and
|
||||
% a Stream to read or write data.
|
||||
%
|
||||
% The following options are available:
|
||||
%
|
||||
% * `alias(+Alias)`: Set an alias to the stream
|
||||
% * `eof_action(+Action)`: Defined what happens if the end of the stream is reached. Values: `error`, `eof_code` and `reset`.
|
||||
% * `reposition(+Boolean)`: Specifies whether repositioning is required for the stream. `false` is the default.
|
||||
% * `type(+Type)`: Type can be `text` or `binary`. Defines the type of the stream, if it's optimized for plain text
|
||||
% or just binary
|
||||
%
|
||||
socket_server_accept(ServerSocket, Client, Stream, Options) :-
|
||||
must_be(var, Client),
|
||||
must_be(var, Stream),
|
||||
@@ -48,10 +81,14 @@ socket_server_accept(ServerSocket, Client, Stream, Options) :-
|
||||
socket_server_accept/4),
|
||||
'$socket_server_accept'(ServerSocket, Client, Stream, Alias, EOFAction, Reposition, Type).
|
||||
|
||||
|
||||
%% socket_server_close(+ServerSocket).
|
||||
%
|
||||
% Stops listening on that ServerSocket. It's recommended to always close a ServerSocket once it's no longer needed
|
||||
socket_server_close(ServerSocket) :-
|
||||
'$socket_server_close'(ServerSocket).
|
||||
|
||||
|
||||
%% current_hostname(-HostName).
|
||||
%
|
||||
% Returns the current hostname of the computer in which Scryer Prolog is executing right now
|
||||
current_hostname(HostName) :-
|
||||
'$current_hostname'(HostName).
|
||||
|
||||
+29
-1
@@ -1,3 +1,29 @@
|
||||
/** Tabling, also called SLG resolution.
|
||||
|
||||
SLG resolution is an alternative execution strategy that sometimes
|
||||
helps to improve termination and performance characters of Prolog
|
||||
predicates.
|
||||
|
||||
To enable this execution strategy for a Prolog predicate, add a
|
||||
`(table)/1` directive, using the prefix operator `table` that this
|
||||
module defines. For example, to enable tabling for the predicate
|
||||
`p/2`, use:
|
||||
|
||||
```
|
||||
:- use_module(library(tabling)).
|
||||
|
||||
:- table p/2.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The possibility to apply different execution strategies is one of
|
||||
the greatest attractions of pure Prolog code, and one of the
|
||||
strongest arguments for keeping to the pure core of Prolog as far
|
||||
as possible.
|
||||
|
||||
Scryer Prolog implements tabling as described by Desouter et al. in [*Tabling as a Library with Delimited Control*](https://www.ijcai.org/Proceedings/16/Papers/619.pdf).
|
||||
*/
|
||||
|
||||
:- module(tabling,
|
||||
[ start_tabling/2, % +Wrapper, :Worker.
|
||||
@@ -138,7 +164,9 @@ activate(Wrapper,Worker,T) :-
|
||||
|
||||
delim(Wrapper,Worker,Table) :-
|
||||
% debug(tabling, 'ACT: ~p on ~p', [Wrapper, Table]),
|
||||
reset(Worker,SourceCall,Continuation),
|
||||
catch(reset(Worker,SourceCall,Continuation),
|
||||
_,
|
||||
fail),
|
||||
( Continuation = none ->
|
||||
( add_answer(Table,Wrapper)
|
||||
-> true %debug(tabling, 'ADD: ~p', [Wrapper])
|
||||
|
||||
@@ -49,12 +49,20 @@
|
||||
:- use_module(library(tabling/double_linked_list)).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
:- attribute executing_all_work/1, worklist_presence/1, wkl_answer_cluster/1, wkl_suspension_cluster/1, wkl_answer_cluster_pointer_flag/1.
|
||||
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ put_atts(X, -executing_all_work(_)),
|
||||
put_atts(X, -worklist_presence(_)),
|
||||
put_atts(X, -wkl_answer_cluster(_)),
|
||||
put_atts(X, -wkl_suspension_cluster(_)),
|
||||
put_atts(X, -wkl_answer_cluster_pointer_flag(_)) }.
|
||||
|
||||
/** <module> Tabling Worklist management
|
||||
|
||||
A batched worklist: a worklist that clusters suspensions and answers as
|
||||
|
||||
@@ -49,9 +49,15 @@
|
||||
]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
:- attribute dll_element/1, dll_next/1, dll_prev/1.
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ put_atts(X, -dll_element(_)),
|
||||
put_atts(X, -dll_next(_)),
|
||||
put_atts(X, -dll_prev(_)) }.
|
||||
|
||||
% A circular double linked list
|
||||
% =============================
|
||||
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(iso_ext)).
|
||||
|
||||
:- attribute table_global_worklist/1.
|
||||
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
attribute_goals(X) --> { put_atts(X, -table_global_worklist(_)) }.
|
||||
|
||||
put_new_global_worklist :-
|
||||
( bb_get(table_global_worklist_initialized, _) ->
|
||||
true
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
:- use_module(library(tabling/batched_worklist)).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(gensym)).
|
||||
:- use_module(library(iso_ext)).
|
||||
|
||||
@@ -63,6 +64,10 @@
|
||||
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ put_atts(X, -table_status(_)),
|
||||
put_atts(X, -newly_created_table_identifiers(_)) }.
|
||||
|
||||
% This file defines the table datastructure.
|
||||
%
|
||||
% The table datastructure contains the following sub-structures:
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(terms)).
|
||||
@@ -53,6 +54,9 @@
|
||||
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ put_atts(X, -trie_table_link(_)) }.
|
||||
|
||||
% This file defines a call pattern trie.
|
||||
%
|
||||
% This data structure keeps the relation between a variant and the
|
||||
|
||||
@@ -45,12 +45,17 @@
|
||||
|
||||
:- use_module(library(assoc)).
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
:- attribute maybe_just/1, children/1.
|
||||
|
||||
verify_attributes(_, _, []).
|
||||
|
||||
attribute_goals(X) -->
|
||||
{ put_atts(X, -maybe_just(_)),
|
||||
put_atts(X, -children(_)) }.
|
||||
|
||||
% Implementation of a prefix tree, a.k.a. trie %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
+59
-40
@@ -1,47 +1,11 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2021 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
This library provides predicates for reasoning about time.
|
||||
|
||||
current_time(T) yields the current system time in an opaque form,
|
||||
called a time stamp. Use format_time//2 to describe strings that
|
||||
contain attributes of the time stamp.
|
||||
|
||||
The nonterminal format_time//2 describes a list of characters that
|
||||
are formatted according to a format string. Usage:
|
||||
|
||||
phrase(format_time(FormatString, TimeStamp), Cs)
|
||||
|
||||
TimeStamp represents a moment in time in an opaque form, as for
|
||||
example obtained by current_time/1.
|
||||
|
||||
FormatString is a list of characters that are interpreted literally,
|
||||
except for the following specifiers (and possibly more in the future):
|
||||
|
||||
%Y year of the time stamp. Example: 2020.
|
||||
%m month number (01-12), zero-padded to 2 digits
|
||||
%d day number (01-31), zero-padded to 2 digits
|
||||
%H hour number (00-24), zero-padded to 2 digits
|
||||
%M minute number (00-59), zero-padded to 2 digits
|
||||
%S second number (00-60), zero-padded to 2 digits
|
||||
%b abbreviated month name, always 3 letters
|
||||
%a abbreviated weekday name, always 3 letters
|
||||
%A full weekday name
|
||||
%j day of the year (001-366), zero-padded to 3 digits
|
||||
%% the literal %
|
||||
|
||||
Example:
|
||||
|
||||
?- current_time(T), phrase(format_time("%d.%m.%Y (%H:%M:%S)", T), Cs).
|
||||
T = [...], Cs = "11.06.2020 (00:24:32)".
|
||||
|
||||
sleep(S) sleeps for S seconds (a floating point number).
|
||||
|
||||
time(Goal) reports the execution time of Goal.
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** This library provides predicates for reasoning about time.
|
||||
*/
|
||||
|
||||
:- module(time, [max_sleep_time/1, sleep/1, time/1, current_time/1, format_time//2]).
|
||||
|
||||
:- use_module(library(format)).
|
||||
@@ -51,10 +15,51 @@
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(charsio), [read_from_chars/2]).
|
||||
|
||||
|
||||
%% current_time(-T)
|
||||
%
|
||||
% Yields the current system time _T_ in an opaque form, called a
|
||||
% _time stamp_. Use `format_time//2` to describe strings that contain
|
||||
% attributes of the time stamp.
|
||||
|
||||
current_time(T) :-
|
||||
'$current_time'(T0),
|
||||
read_from_chars(T0, T).
|
||||
|
||||
%% format_time(FormatString, TimeStamp)//
|
||||
%
|
||||
% The nonterminal format_time//2 describes a list of characters that
|
||||
% are formatted according to a format string. Usage:
|
||||
%
|
||||
% ```
|
||||
% phrase(format_time(FormatString, TimeStamp), Cs)
|
||||
% ```
|
||||
%
|
||||
% TimeStamp represents a moment in time in an opaque form, as for
|
||||
% example obtained by `current_time/1`.
|
||||
%
|
||||
% FormatString is a list of characters that are interpreted literally,
|
||||
% except for the following specifiers (and possibly more in the future):
|
||||
%
|
||||
% | `%Y` | year of the time stamp. Example: 2020. |
|
||||
% | `%m` | month number (01-12), zero-padded to 2 digits |
|
||||
% | `%d` | day number (01-31), zero-padded to 2 digits |
|
||||
% | `%H` | hour number (00-24), zero-padded to 2 digits |
|
||||
% | `%M` | minute number (00-59), zero-padded to 2 digits |
|
||||
% | `%S` | second number (00-60), zero-padded to 2 digits |
|
||||
% | `%b` | abbreviated month name, always 3 letters |
|
||||
% | `%a` | abbreviated weekday name, always 3 letters |
|
||||
% | `%A` | full weekday name |
|
||||
% | `%j` | day of the year (001-366), zero-padded to 3 digits |
|
||||
% | `%%` | the literal `%` |
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- current_time(T), phrase(format_time("%d.%m.%Y (%H:%M:%S)", T), Cs).
|
||||
% T = [...], Cs = "11.06.2020 (00:24:32)".
|
||||
% ```
|
||||
|
||||
format_time([], _) --> [].
|
||||
format_time(['%','%'|Fs], T) --> !, "%", format_time(Fs, T).
|
||||
format_time(['%',Spec|Fs], T) --> !,
|
||||
@@ -65,8 +70,17 @@ format_time(['%',Spec|Fs], T) --> !,
|
||||
format_time(Fs, T).
|
||||
format_time([F|Fs], T) --> [F], format_time(Fs, T).
|
||||
|
||||
%% max_sleep_time(T)
|
||||
%
|
||||
% The maximum admissible time span for `sleep/1`.
|
||||
|
||||
max_sleep_time(0xfffffffffffffbff).
|
||||
|
||||
|
||||
%% sleep(S)
|
||||
%
|
||||
% Sleeps for S seconds (a floating point number or integer).
|
||||
|
||||
sleep(T) :-
|
||||
builtins:must_be_number(T, sleep),
|
||||
( T < 0 ->
|
||||
@@ -91,6 +105,11 @@ time_next_id(N) :-
|
||||
),
|
||||
asserta(time_id(N)).
|
||||
|
||||
|
||||
%% time(Goal)
|
||||
%
|
||||
% Reports the execution time of Goal.
|
||||
|
||||
time(Goal) :-
|
||||
'$cpu_now'(T0),
|
||||
time_next_id(ID),
|
||||
|
||||
+170
-167
@@ -53,7 +53,7 @@
|
||||
connect_ugraph/3 % +Graph1, -Start, -Graph
|
||||
]).
|
||||
|
||||
/** <module> Graph manipulation library
|
||||
/** Graph manipulation library
|
||||
|
||||
The S-representation of a graph is a list of (vertex-neighbours) pairs,
|
||||
where the pairs are in standard order (as produced by keysort) and the
|
||||
@@ -61,55 +61,56 @@ neighbours of each vertex are also in standard order (as produced by
|
||||
sort). This form is convenient for many calculations.
|
||||
|
||||
A new UGraph from raw data can be created using
|
||||
vertices_edges_to_ugraph/3.
|
||||
`vertices_edges_to_ugraph/3`.
|
||||
|
||||
Adapted to support some of the functionality of the SICStus ugraphs
|
||||
library by Vitor Santos Costa.
|
||||
|
||||
Ported from YAP 5.0.1 to SWI-Prolog by Jan Wielemaker.
|
||||
|
||||
@author R.A.O'Keefe
|
||||
@author Vitor Santos Costa
|
||||
@author Jan Wielemaker
|
||||
@license BSD-2 or Artistic 2.0
|
||||
Ported from SWI-Prolog to Scryer by [Adrián Arroyo Calle](https://adrianistan.eu)
|
||||
|
||||
License: BSD-2 or Artistic 2.0
|
||||
*/
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(pairs)).
|
||||
:- use_module(library(ordsets)).
|
||||
|
||||
%! vertices(+Graph, -Vertices)
|
||||
%% vertices(+Graph, -Vertices)
|
||||
%
|
||||
% Unify Vertices with all vertices appearing in Graph. Example:
|
||||
% Unify Vertices with all vertices appearing in Graph. Example:
|
||||
%
|
||||
% ?- vertices([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
|
||||
% L = [1, 2, 3, 4, 5]
|
||||
% ```
|
||||
% ?- vertices([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
|
||||
% L = [1, 2, 3, 4, 5]
|
||||
% ```
|
||||
|
||||
vertices([], []) :- !.
|
||||
vertices([Vertex-_|Graph], [Vertex|Vertices]) :-
|
||||
vertices(Graph, Vertices).
|
||||
|
||||
|
||||
%! vertices_edges_to_ugraph(+Vertices, +Edges, -UGraph) is det.
|
||||
%% vertices_edges_to_ugraph(+Vertices, +Edges, -UGraph) is det.
|
||||
%
|
||||
% Create a UGraph from Vertices and edges. Given a graph with a
|
||||
% set of Vertices and a set of Edges, Graph must unify with the
|
||||
% corresponding S-representation. Note that the vertices without
|
||||
% edges will appear in Vertices but not in Edges. Moreover, it is
|
||||
% sufficient for a vertice to appear in Edges.
|
||||
% Create a UGraph from Vertices and edges. Given a graph with a
|
||||
% set of Vertices and a set of Edges, Graph must unify with the
|
||||
% corresponding S-representation. Note that the vertices without
|
||||
% edges will appear in Vertices but not in Edges. Moreover, it is
|
||||
% sufficient for a vertice to appear in Edges.
|
||||
%
|
||||
% ==
|
||||
% ?- vertices_edges_to_ugraph([],[1-3,2-4,4-5,1-5], L).
|
||||
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[]]
|
||||
% ==
|
||||
% ```
|
||||
% ?- vertices_edges_to_ugraph([],[1-3,2-4,4-5,1-5], L).
|
||||
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[]]
|
||||
% ```
|
||||
%
|
||||
% In this case all vertices are defined implicitly. The next
|
||||
% example shows three unconnected vertices:
|
||||
%
|
||||
% In this case all vertices are defined implicitly. The next
|
||||
% example shows three unconnected vertices:
|
||||
%
|
||||
% ==
|
||||
% ?- vertices_edges_to_ugraph([6,7,8],[1-3,2-4,4-5,1-5], L).
|
||||
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[], 6-[], 7-[], 8-[]]
|
||||
% ==
|
||||
% ```
|
||||
% ?- vertices_edges_to_ugraph([6,7,8],[1-3,2-4,4-5,1-5], L).
|
||||
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[], 6-[], 7-[], 8-[]]
|
||||
% ```
|
||||
|
||||
vertices_edges_to_ugraph(Vertices, Edges, Graph) :-
|
||||
sort(Edges, EdgeSet),
|
||||
@@ -119,15 +120,15 @@ vertices_edges_to_ugraph(Vertices, Edges, Graph) :-
|
||||
p_to_s_group(VertexSet, EdgeSet, Graph).
|
||||
|
||||
|
||||
%! add_vertices(+Graph, +Vertices, -NewGraph)
|
||||
%% add_vertices(+Graph, +Vertices, -NewGraph)
|
||||
%
|
||||
% Unify NewGraph with a new graph obtained by adding the list of
|
||||
% Vertices to Graph. Example:
|
||||
% Unify NewGraph with a new graph obtained by adding the list of
|
||||
% Vertices to Graph. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- add_vertices([1-[3,5],2-[]], [0,1,2,9], NG).
|
||||
% NG = [0-[], 1-[3,5], 2-[], 9-[]]
|
||||
% ```
|
||||
% ```
|
||||
% ?- add_vertices([1-[3,5],2-[]], [0,1,2,9], NG).
|
||||
% NG = [0-[], 1-[3,5], 2-[], 9-[]]
|
||||
% ```
|
||||
|
||||
% replace with real msort/2 when available
|
||||
msort_(List, Sorted) :-
|
||||
@@ -159,23 +160,18 @@ add_empty_vertices([], []).
|
||||
add_empty_vertices([V|G], [V-[]|NG]) :-
|
||||
add_empty_vertices(G, NG).
|
||||
|
||||
%! del_vertices(+Graph, +Vertices, -NewGraph) is det.
|
||||
%% del_vertices(+Graph, +Vertices, -NewGraph) is det.
|
||||
%
|
||||
% Unify NewGraph with a new graph obtained by deleting the list of
|
||||
% Vertices and all the edges that start from or go to a vertex in
|
||||
% Vertices to the Graph. Example:
|
||||
% Unify NewGraph with a new graph obtained by deleting the list of
|
||||
% Vertices and all the edges that start from or go to a vertex in
|
||||
% Vertices to the Graph. Example:
|
||||
%
|
||||
% ==
|
||||
% ?- del_vertices([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[2,6],8-[]],
|
||||
% [2,1],
|
||||
% NL).
|
||||
% NL = [3-[],4-[5],5-[],6-[],7-[6],8-[]]
|
||||
% ==
|
||||
%
|
||||
% @compat Upto 5.6.48 the argument order was (+Vertices, +Graph,
|
||||
% -NewGraph). Both YAP and SWI-Prolog have changed the argument
|
||||
% order for compatibility with recent SICStus as well as
|
||||
% consistency with del_edges/3.
|
||||
% ```
|
||||
% ?- del_vertices([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[2,6],8-[]],
|
||||
% [2,1],
|
||||
% NL).
|
||||
% NL = [3-[],4-[5],5-[],6-[],7-[6],8-[]]
|
||||
% ```
|
||||
|
||||
del_vertices(Graph, Vertices, NewGraph) :-
|
||||
sort(Vertices, V1), % JW: was msort
|
||||
@@ -204,32 +200,32 @@ split_on_del_vertices(>, V, Edges, [_|Vs], Vs, V1, [V-NEdges|NG], NG) :-
|
||||
ord_subtract(Edges, V1, NEdges).
|
||||
split_on_del_vertices(=, _, _, [_|Vs], Vs, _, NG, NG).
|
||||
|
||||
%! add_edges(+Graph, +Edges, -NewGraph)
|
||||
%% add_edges(+Graph, +Edges, -NewGraph)
|
||||
%
|
||||
% Unify NewGraph with a new graph obtained by adding the list of Edges
|
||||
% to Graph. Example:
|
||||
% Unify NewGraph with a new graph obtained by adding the list of Edges
|
||||
% to Graph. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- add_edges([1-[3,5],2-[4],3-[],4-[5],
|
||||
% 5-[],6-[],7-[],8-[]],
|
||||
% [1-6,2-3,3-2,5-7,3-2,4-5],
|
||||
% NL).
|
||||
% NL = [1-[3,5,6], 2-[3,4], 3-[2], 4-[5],
|
||||
% 5-[7], 6-[], 7-[], 8-[]]
|
||||
% ```
|
||||
% ```
|
||||
% ?- add_edges([1-[3,5],2-[4],3-[],4-[5],
|
||||
% 5-[],6-[],7-[],8-[]],
|
||||
% [1-6,2-3,3-2,5-7,3-2,4-5],
|
||||
% NL).
|
||||
% NL = [1-[3,5,6], 2-[3,4], 3-[2], 4-[5],
|
||||
% 5-[7], 6-[], 7-[], 8-[]]
|
||||
% ```
|
||||
|
||||
add_edges(Graph, Edges, NewGraph) :-
|
||||
p_to_s_graph(Edges, G1),
|
||||
ugraph_union(Graph, G1, NewGraph).
|
||||
|
||||
%! ugraph_union(+Graph1, +Graph2, -NewGraph)
|
||||
%% ugraph_union(+Graph1, +Graph2, -NewGraph)
|
||||
%
|
||||
% NewGraph is the union of Graph1 and Graph2. Example:
|
||||
% NewGraph is the union of Graph1 and Graph2. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- ugraph_union([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
|
||||
% L = [1-[2], 2-[3,4], 3-[1,2,4]]
|
||||
% ```
|
||||
% ```
|
||||
% ?- ugraph_union([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
|
||||
% L = [1-[2], 2-[3,4], 3-[1,2,4]]
|
||||
% ```
|
||||
|
||||
ugraph_union(Set1, [], Set1) :- !.
|
||||
ugraph_union([], Set2, Set2) :- !.
|
||||
@@ -245,25 +241,25 @@ ugraph_union(<, Head1, Tail1, Head2, Tail2, [Head1|Union]) :-
|
||||
ugraph_union(>, Head1, Tail1, Head2, Tail2, [Head2|Union]) :-
|
||||
ugraph_union([Head1|Tail1], Tail2, Union).
|
||||
|
||||
%! del_edges(+Graph, +Edges, -NewGraph)
|
||||
%% del_edges(+Graph, +Edges, -NewGraph)
|
||||
%
|
||||
% Unify NewGraph with a new graph obtained by removing the list of
|
||||
% Edges from Graph. Notice that no vertices are deleted. Example:
|
||||
% Unify NewGraph with a new graph obtained by removing the list of
|
||||
% Edges from Graph. Notice that no vertices are deleted. Example:
|
||||
%
|
||||
% ```
|
||||
% ?- del_edges([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[],8-[]],
|
||||
% [1-6,2-3,3-2,5-7,3-2,4-5,1-3],
|
||||
% NL).
|
||||
% NL = [1-[5],2-[4],3-[],4-[],5-[],6-[],7-[],8-[]]
|
||||
% ```
|
||||
% ```
|
||||
% ?- del_edges([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[],8-[]],
|
||||
% [1-6,2-3,3-2,5-7,3-2,4-5,1-3],
|
||||
% NL).
|
||||
% NL = [1-[5],2-[4],3-[],4-[],5-[],6-[],7-[],8-[]]
|
||||
% ```
|
||||
|
||||
del_edges(Graph, Edges, NewGraph) :-
|
||||
p_to_s_graph(Edges, G1),
|
||||
graph_subtract(Graph, G1, NewGraph).
|
||||
|
||||
%! graph_subtract(+Set1, +Set2, ?Difference)
|
||||
%% graph_subtract(+Set1, +Set2, ?Difference)
|
||||
%
|
||||
% Is based on ord_subtract
|
||||
% Is based on `ord_subtract/3`
|
||||
|
||||
graph_subtract(Set1, [], Set1) :- !.
|
||||
graph_subtract([], _, []).
|
||||
@@ -279,12 +275,14 @@ graph_subtract(<, Head1, Tail1, Head2, Tail2, [Head1|Difference]) :-
|
||||
graph_subtract(>, Head1, Tail1, _, Tail2, Difference) :-
|
||||
graph_subtract([Head1|Tail1], Tail2, Difference).
|
||||
|
||||
%! edges(+Graph, -Edges)
|
||||
%% edges(+Graph, -Edges)
|
||||
%
|
||||
% Unify Edges with all edges appearing in Graph. Example:
|
||||
% Unify Edges with all edges appearing in Graph. Example:
|
||||
%
|
||||
% ?- edges([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
|
||||
% L = [1-3, 1-5, 2-4, 4-5]
|
||||
% ```
|
||||
% ?- edges([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
|
||||
% L = [1-3, 1-5, 2-4, 4-5]
|
||||
% ```
|
||||
|
||||
edges(Graph, Edges) :-
|
||||
s_to_p_graph(Graph, Edges).
|
||||
@@ -324,15 +322,15 @@ s_to_p_graph([], _, P_Graph, P_Graph) :- !.
|
||||
s_to_p_graph([Neib|Neibs], Vertex, [Vertex-Neib|P], Rest_P) :-
|
||||
s_to_p_graph(Neibs, Vertex, P, Rest_P).
|
||||
|
||||
%! transitive_closure(+Graph, -Closure)
|
||||
%% transitive_closure(+Graph, -Closure)
|
||||
%
|
||||
% Generate the graph Closure as the transitive closure of Graph.
|
||||
% Example:
|
||||
% Generate the graph Closure as the transitive closure of Graph.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- transitive_closure([1-[2,3],2-[4,5],4-[6]],L).
|
||||
% L = [1-[2,3,4,5,6], 2-[4,5,6], 4-[6]]
|
||||
% ```
|
||||
% ```
|
||||
% ?- transitive_closure([1-[2,3],2-[4,5],4-[6]],L).
|
||||
% L = [1-[2,3,4,5,6], 2-[4,5,6], 4-[6]]
|
||||
% ```
|
||||
|
||||
transitive_closure(Graph, Closure) :-
|
||||
warshall(Graph, Graph, Closure).
|
||||
@@ -354,23 +352,18 @@ warshall([X-Neibs|G], V, Y, [X-Neibs|NewG]) :-
|
||||
warshall(G, V, Y, NewG).
|
||||
warshall([], _, _, []).
|
||||
|
||||
%! transpose_ugraph(Graph, NewGraph) is det.
|
||||
%% transpose_ugraph(Graph, NewGraph) is det.
|
||||
%
|
||||
% Unify NewGraph with a new graph obtained from Graph by replacing
|
||||
% all edges of the form V1-V2 by edges of the form V2-V1. The cost
|
||||
% is O(|V|*log(|V|)). Notice that an undirected graph is its own
|
||||
% transpose. Example:
|
||||
% Unify NewGraph with a new graph obtained from Graph by replacing
|
||||
% all edges of the form V1-V2 by edges of the form V2-V1. The cost
|
||||
% is O(|V|\*log(|V|)). Notice that an undirected graph is its own
|
||||
% transpose. Example:
|
||||
%
|
||||
% ==
|
||||
% ?- transpose([1-[3,5],2-[4],3-[],4-[5],
|
||||
% 5-[],6-[],7-[],8-[]], NL).
|
||||
% NL = [1-[],2-[],3-[1],4-[2],5-[1,4],6-[],7-[],8-[]]
|
||||
% ==
|
||||
%
|
||||
% @compat This predicate used to be known as transpose/2.
|
||||
% Following SICStus 4, we reserve transpose/2 for matrix
|
||||
% transposition and renamed ugraph transposition to
|
||||
% transpose_ugraph/2.
|
||||
% ```
|
||||
% ?- transpose([1-[3,5],2-[4],3-[],4-[5],
|
||||
% 5-[],6-[],7-[],8-[]], NL).
|
||||
% NL = [1-[],2-[],3-[1],4-[2],5-[1,4],6-[],7-[],8-[]]
|
||||
% ```
|
||||
|
||||
transpose_ugraph(Graph, NewGraph) :-
|
||||
edges(Graph, Edges),
|
||||
@@ -382,13 +375,15 @@ flip_edges([], []).
|
||||
flip_edges([Key-Val|Pairs], [Val-Key|Flipped]) :-
|
||||
flip_edges(Pairs, Flipped).
|
||||
|
||||
%! compose(+LeftGraph, +RightGraph, -NewGraph)
|
||||
%% compose(+LeftGraph, +RightGraph, -NewGraph)
|
||||
%
|
||||
% Compose NewGraph by connecting the _drains_ of LeftGraph to the
|
||||
% _sources_ of RightGraph. Example:
|
||||
% Compose NewGraph by connecting the _drains_ of LeftGraph to the
|
||||
% _sources_ of RightGraph. Example:
|
||||
%
|
||||
% ?- compose([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
|
||||
% L = [1-[4], 2-[1,2,4], 3-[]]
|
||||
% ```
|
||||
% ?- compose([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
|
||||
% L = [1-[4], 2-[1,2,4], 3-[]]
|
||||
% ```
|
||||
|
||||
compose(G1, G2, Composition) :-
|
||||
vertices(G1, V1),
|
||||
@@ -423,21 +418,17 @@ compose1(=, V1, Vs1, V1, N2, G2, SoFar, Comp) :-
|
||||
ord_union(N2, SoFar, Next),
|
||||
compose1(Vs1, G2, Next, Comp).
|
||||
|
||||
%! top_sort(+Graph, -Sorted) is semidet.
|
||||
%! top_sort(+Graph, -Sorted, ?Tail) is semidet.
|
||||
%% top_sort(+Graph, -Sorted) is semidet.
|
||||
%
|
||||
% Sorted is a topological sorted list of nodes in Graph. A
|
||||
% toplogical sort is possible if the graph is connected and
|
||||
% acyclic. In the example we show how topological sorting works
|
||||
% for a linear graph:
|
||||
% Sorted is a topological sorted list of nodes in Graph. A
|
||||
% toplogical sort is possible if the graph is connected and
|
||||
% acyclic. In the example we show how topological sorting works
|
||||
% for a linear graph:
|
||||
%
|
||||
% ==
|
||||
% ?- top_sort([1-[2], 2-[3], 3-[]], L).
|
||||
% L = [1, 2, 3]
|
||||
% ==
|
||||
%
|
||||
% The predicate top_sort/3 is a difference list version of
|
||||
% top_sort/2.
|
||||
% ```
|
||||
% ?- top_sort([1-[2], 2-[3], 3-[]], L).
|
||||
% L = [1, 2, 3]
|
||||
% ```
|
||||
|
||||
top_sort(Graph, Sorted) :-
|
||||
vertices_and_zeros(Graph, Vertices, Counts0),
|
||||
@@ -445,6 +436,11 @@ top_sort(Graph, Sorted) :-
|
||||
select_zeros(Counts1, Vertices, Zeros),
|
||||
top_sort(Zeros, Sorted, Graph, Vertices, Counts1).
|
||||
|
||||
%% top_sort(+Graph, -Sorted, ?Tail) is semidet.
|
||||
%
|
||||
% The predicate `top_sort/3` is a difference list version of
|
||||
% `top_sort/2`.
|
||||
|
||||
top_sort(Graph, Sorted0, Sorted) :-
|
||||
vertices_and_zeros(Graph, Vertices, Counts0),
|
||||
count_edges(Graph, Vertices, Counts0, Counts1),
|
||||
@@ -520,17 +516,21 @@ decr_list(Neibs, [_|Vertices], [N|Counts1], [N|Counts2], Zi, Zo) :-
|
||||
decr_list(Neibs, Vertices, Counts1, Counts2, Zi, Zo).
|
||||
|
||||
|
||||
%! neighbors(+Vertex, +Graph, -Neigbours) is det.
|
||||
%! neighbours(+Vertex, +Graph, -Neigbours) is det.
|
||||
|
||||
%% neighbours(+Vertex, +Graph, -Neigbours) is det.
|
||||
%
|
||||
% Neigbours is a sorted list of the neighbours of Vertex in Graph.
|
||||
% Example:
|
||||
% Neigbours is a sorted list of the neighbours of Vertex in Graph.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- neighbours(4,[1-[3,5],2-[4],3-[],
|
||||
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
|
||||
% NL = [1,2,7,5]
|
||||
% ```
|
||||
% ```
|
||||
% ?- neighbours(4,[1-[3,5],2-[4],3-[],
|
||||
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
|
||||
% NL = [1,2,7,5]
|
||||
% ```
|
||||
|
||||
%% neighbors(+Vertex, +Graph, -Neigbours) is det.
|
||||
%
|
||||
% Same as `neighbours/3`.
|
||||
|
||||
neighbors(Vertex, Graph, Neig) :-
|
||||
neighbours(Vertex, Graph, Neig).
|
||||
@@ -542,24 +542,24 @@ neighbours(V,[_|G],Neig) :-
|
||||
neighbours(V,G,Neig).
|
||||
|
||||
|
||||
%! connect_ugraph(+UGraphIn, -Start, -UGraphOut) is det.
|
||||
%% connect_ugraph(+UGraphIn, -Start, -UGraphOut) is det.
|
||||
%
|
||||
% Adds Start as an additional vertex that is connected to all vertices
|
||||
% in UGraphIn. This can be used to create an topological sort for a
|
||||
% not connected graph. Start is before any vertex in UGraphIn in the
|
||||
% standard order of terms. No vertex in UGraphIn can be a variable.
|
||||
% Adds Start as an additional vertex that is connected to all vertices
|
||||
% in UGraphIn. This can be used to create an topological sort for a
|
||||
% not connected graph. Start is before any vertex in UGraphIn in the
|
||||
% standard order of terms. No vertex in UGraphIn can be a variable.
|
||||
%
|
||||
% Can be used to order a not-connected graph as follows:
|
||||
% Can be used to order a not-connected graph as follows:
|
||||
%
|
||||
% ```
|
||||
% top_sort_unconnected(Graph, Vertices) :-
|
||||
% ( top_sort(Graph, Vertices)
|
||||
% -> true
|
||||
% ; connect_ugraph(Graph, Start, Connected),
|
||||
% top_sort(Connected, Ordered0),
|
||||
% Ordered0 = [Start|Vertices]
|
||||
% ).
|
||||
% ```
|
||||
% ```
|
||||
% top_sort_unconnected(Graph, Vertices) :-
|
||||
% ( top_sort(Graph, Vertices)
|
||||
% -> true
|
||||
% ; connect_ugraph(Graph, Start, Connected),
|
||||
% top_sort(Connected, Ordered0),
|
||||
% Ordered0 = [Start|Vertices]
|
||||
% ).
|
||||
% ```
|
||||
|
||||
connect_ugraph([], 0, []) :- !.
|
||||
connect_ugraph(Graph, Start, [Start-Vertices|Graph]) :-
|
||||
@@ -567,12 +567,12 @@ connect_ugraph(Graph, Start, [Start-Vertices|Graph]) :-
|
||||
Vertices = [First|_],
|
||||
before(First, Start).
|
||||
|
||||
%! before(+Term, -Before) is det.
|
||||
%% before(+Term, -Before) is det.
|
||||
%
|
||||
% Unify Before to a term that comes before Term in the standard
|
||||
% order of terms.
|
||||
% Unify Before to a term that comes before Term in the standard
|
||||
% order of terms.
|
||||
%
|
||||
% @error instantiation_error if Term is unbound.
|
||||
% Throws `instantiation_error` if Term is unbound.
|
||||
|
||||
before(X, _) :-
|
||||
var(X),
|
||||
@@ -585,21 +585,22 @@ before(Number, Start) :-
|
||||
before(_, 0).
|
||||
|
||||
|
||||
%! complement(+UGraphIn, -UGraphOut)
|
||||
%% complement(+UGraphIn, -UGraphOut)
|
||||
%
|
||||
% UGraphOut is a ugraph with an edge between all vertices that are
|
||||
% _not_ connected in UGraphIn and all edges from UGraphIn removed.
|
||||
% Example:
|
||||
% UGraphOut is a ugraph with an edge between all vertices that are
|
||||
% _not_ connected in UGraphIn and all edges from UGraphIn removed.
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- complement([1-[3,5],2-[4],3-[],
|
||||
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
|
||||
% NL = [1-[2,4,6,7,8],2-[1,3,5,6,7,8],3-[1,2,4,5,6,7,8],
|
||||
% 4-[3,5,6,8],5-[1,2,3,4,6,7,8],6-[1,2,3,4,5,7,8],
|
||||
% 7-[1,2,3,4,5,6,8],8-[1,2,3,4,5,6,7]]
|
||||
% ```
|
||||
%
|
||||
% @tbd Simple two-step algorithm. You could be smarter, I suppose.
|
||||
% ```
|
||||
% ?- complement([1-[3,5],2-[4],3-[],
|
||||
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
|
||||
% NL = [1-[2,4,6,7,8],2-[1,3,5,6,7,8],3-[1,2,4,5,6,7,8],
|
||||
% 4-[3,5,6,8],5-[1,2,3,4,6,7,8],6-[1,2,3,4,5,7,8],
|
||||
% 7-[1,2,3,4,5,6,8],8-[1,2,3,4,5,6,7]]
|
||||
% ```
|
||||
|
||||
|
||||
% TODO: Simple two-step algorithm. You could be smarter, I suppose.
|
||||
|
||||
complement(G, NG) :-
|
||||
vertices(G,Vs),
|
||||
@@ -611,13 +612,15 @@ complement([V-Ns|G], Vs, [V-INs|NG]) :-
|
||||
ord_subtract(Vs,Ns1,INs),
|
||||
complement(G, Vs, NG).
|
||||
|
||||
%! reachable(+Vertex, +UGraph, -Vertices)
|
||||
%% reachable(+Vertex, +UGraph, -Vertices)
|
||||
%
|
||||
% True when Vertices is an ordered set of vertices reachable in
|
||||
% UGraph, including Vertex. Example:
|
||||
% True when Vertices is an ordered set of vertices reachable in
|
||||
% UGraph, including Vertex. Example:
|
||||
%
|
||||
% ?- reachable(1,[1-[3,5],2-[4],3-[],4-[5],5-[]],V).
|
||||
% V = [1, 3, 5]
|
||||
% ```
|
||||
% ?- reachable(1,[1-[3,5],2-[4],3-[],4-[5],5-[]],V).
|
||||
% V = [1, 3, 5]
|
||||
% ```
|
||||
|
||||
reachable(N, G, Rs) :-
|
||||
reachable([N], G, [N], Rs).
|
||||
|
||||
+34
-16
@@ -1,25 +1,32 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written in February 2021 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
|
||||
Part of Scryer-Prolog
|
||||
This library provides reasoning about UUID (only version 4 right now).
|
||||
There are three predicates:
|
||||
* uuidv4/1, to generate a new UUIDv4
|
||||
* uuidv4_string/1, to generate a new UUIDv4 in string hex representation
|
||||
* uuid_string/2, to converte between UUID list of bytes and UUID hex representation
|
||||
|
||||
Examples:
|
||||
?- uuidv4(X).
|
||||
X = [42,147,248,242,117,196,79,2,129,159|...].
|
||||
?- uuidv4_string(X).
|
||||
X = "428499fc-76e3-4240- ...".
|
||||
?- uuidv4(X), uuid_string(X, S).
|
||||
X = [173,12,244,152,139,118,64,139,137,4|...], S = "ad0cf498-8b76-408b- ...".
|
||||
?- uuid_string(X, "61ae692e-eaf6-4199-8dd3-9f01db70a20b").
|
||||
X = [97,174,105,46,234,246,65,153,141,211|...].
|
||||
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/**
|
||||
This library provides reasoning and working with [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)
|
||||
(only version 4 right now).
|
||||
|
||||
There are three predicates:
|
||||
|
||||
* `uuidv4/1`, to generate a new UUIDv4
|
||||
* `uuidv4_string/1`, to generate a new UUIDv4 in string hex representation
|
||||
* `uuid_string/2`, to converte between UUID list of bytes and UUID hex representation
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
?- uuidv4(X).
|
||||
X = [42,147,248,242,117,196,79,2,129,159|...].
|
||||
?- uuidv4_string(X).
|
||||
X = "428499fc-76e3-4240- ...".
|
||||
?- uuidv4(X), uuid_string(X, S).
|
||||
X = [173,12,244,152,139,118,64,139,137,4|...], S = "ad0cf498-8b76-408b- ...".
|
||||
?- uuid_string(X, "61ae692e-eaf6-4199-8dd3-9f01db70a20b").
|
||||
X = [97,174,105,46,234,246,65,153,141,211|...].
|
||||
*/
|
||||
|
||||
:- module(uuid, [
|
||||
uuidv4/1,
|
||||
uuidv4_string/1,
|
||||
@@ -39,6 +46,10 @@ clock_seq_hi_and_res_clock_seq_low - 2
|
||||
node - 6
|
||||
UUID v4 can be generated from a set of 16 random bytes: https://www.rfc-archive.org/getrfc.php?rfc=4122#gsc.tab=0 (section 4.4)
|
||||
*/
|
||||
|
||||
%% uuidv4(-Uuid).
|
||||
%
|
||||
% Generates a new UUID v4 (random). It unifies with a list of bytes.
|
||||
uuidv4(Uuid) :-
|
||||
crypto_n_random_bytes(16, Bytes),
|
||||
Bytes = [B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16],
|
||||
@@ -52,8 +63,15 @@ uuidv4(Uuid) :-
|
||||
byte_bits(NewTimeHi, NewBitsTimeHi),
|
||||
Uuid = [B1, B2, B3, B4, B5, B6, NewTimeHi, B8, NewClockSeqHi0, B10, B11, B12, B13, B14, B15, B16].
|
||||
|
||||
%% uuidv4_string(-UuidString).
|
||||
%
|
||||
% Generates a new UUID v4 (random). It unifies with a string representation of the UUID.
|
||||
% It is equivalent of calling `uuidv4/1` followed by `uuid_string/2`.
|
||||
uuidv4_string(String) :- uuidv4(Uuid), uuid_string(Uuid, String).
|
||||
|
||||
%% uuid_string(?UuidBytes, ?UuidString).
|
||||
%
|
||||
% Translates between the bytes representation and the string representation of the same UUID.
|
||||
uuid_string(Uuid, String) :-
|
||||
Uuid = [B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16],
|
||||
phrase(uuid_([S1, S2, S3, S4, S5]), String),
|
||||
|
||||
+162
-161
@@ -100,214 +100,215 @@
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(si)).
|
||||
|
||||
/** <module> Select nodes in an XML DOM
|
||||
/** Select nodes in an XML DOM
|
||||
|
||||
The library xpath.pl provides predicates to select nodes from an XML DOM
|
||||
tree as produced by library(sgml) based on descriptions inspired by the
|
||||
XPath language.
|
||||
tree as produced by `library(sgml)` based on descriptions inspired by the
|
||||
[XPath language](http://www.w3.org/TR/xpath).
|
||||
|
||||
The predicate xpath/3 selects a sub-structure of the DOM
|
||||
The predicate `xpath/3` selects a sub-structure of the DOM
|
||||
non-deterministically based on an XPath-like specification. Not all
|
||||
selectors of XPath are implemented, but the ability to mix xpath/3 calls
|
||||
selectors of XPath are implemented, but the ability to mix `xpath/3` calls
|
||||
with arbitrary Prolog code provides a powerful tool for extracting
|
||||
information from XML parse-trees.
|
||||
|
||||
@see http://www.w3.org/TR/xpath
|
||||
*/
|
||||
|
||||
element_name(element(Name,_,_), Name).
|
||||
element_attributes(element(_,Attributes,_), Attributes).
|
||||
element_content(element(_,_,Content), Content).
|
||||
|
||||
%! xpath_chk(+DOM, +Spec, ?Content) is semidet.
|
||||
%% xpath_chk(+DOM, +Spec, ?Content) is semidet.
|
||||
%
|
||||
% Semi-deterministic version of xpath/3.
|
||||
% Semi-deterministic version of `xpath/3`.
|
||||
|
||||
xpath_chk(DOM, Spec, Content) :-
|
||||
xpath(DOM, Spec, Content),
|
||||
!.
|
||||
|
||||
%! xpath(+DOM, +Spec, ?Content) is nondet.
|
||||
%% xpath(+DOM, +Spec, ?Content) is nondet.
|
||||
%
|
||||
% Match an element in a DOM structure. The syntax is inspired by
|
||||
% XPath, using () rather than [] to select inside an element.
|
||||
% First we can construct paths using / and //:
|
||||
% Match an element in a DOM structure. The syntax is inspired by
|
||||
% XPath, using () rather than [] to select inside an element.
|
||||
% First we can construct paths using / and //:
|
||||
%
|
||||
% $ =|//|=Term :
|
||||
% Select any node in the DOM matching term.
|
||||
% $ =|/|=Term :
|
||||
% Match the root against Term.
|
||||
% $ Term :
|
||||
% Select the immediate children of the root matching Term.
|
||||
% - *//Term*
|
||||
% Select any node in the DOM matching term.
|
||||
%
|
||||
% The Terms above are of type _callable_. The functor specifies
|
||||
% the element name. The element name '*' refers to any element.
|
||||
% The name =self= refers to the top-element itself and is often
|
||||
% used for processing matches of an earlier xpath/3 query. A term
|
||||
% NS:Term refers to an XML name in the namespace NS. Optional
|
||||
% arguments specify additional constraints and functions. The
|
||||
% arguments are processed from left to right. Defined conditional
|
||||
% argument values are:
|
||||
% - */Term*
|
||||
% Match the root against Term.
|
||||
%
|
||||
% $ index(?Index) :
|
||||
% True if the element is the Index-th child of its parent,
|
||||
% where 1 denotes the first child. Index can be one of:
|
||||
% $ `Var` :
|
||||
% `Var` is unified with the index of the matched element.
|
||||
% $ =last= :
|
||||
% True for the last element.
|
||||
% $ =last= - `IntExpr` :
|
||||
% True for the last-minus-nth element. For example,
|
||||
% `last-1` is the element directly preceding the last one.
|
||||
% $ `IntExpr` :
|
||||
% True for the element whose index equals `IntExpr`.
|
||||
% $ Integer :
|
||||
% The N-th element with the given name, with 1 denoting the
|
||||
% first element. Same as index(Integer).
|
||||
% $ =last= :
|
||||
% The last element with the given name. Same as
|
||||
% index(last).
|
||||
% $ =last= - IntExpr :
|
||||
% The IntExpr-th element before the last.
|
||||
% Same as index(last-IntExpr).
|
||||
% - *Term*
|
||||
% Select the immediate children of the root matching Term.
|
||||
%
|
||||
% Defined function argument values are:
|
||||
% The Terms above are of type _callable_. The functor specifies
|
||||
% the element name. The element name `*` refers to any element.
|
||||
% The name _self_ refers to the top-element itself and is often
|
||||
% used for processing matches of an earlier `xpath/3` query. A term
|
||||
% NS:Term refers to an XML name in the namespace NS. Optional
|
||||
% arguments specify additional constraints and functions. The
|
||||
% arguments are processed from left to right. Defined conditional
|
||||
% argument values are:
|
||||
%
|
||||
% $ =self= :
|
||||
% Evaluate to the entire element
|
||||
% $ =content= :
|
||||
% Evaluate to the content of the element (a list)
|
||||
% $ =text= :
|
||||
% Evaluates to all text from the sub-tree, represented
|
||||
% as a list of characters.
|
||||
% $ `text(atom)` :
|
||||
% Evaluates to all text from the sub-tree as an atom.
|
||||
% $ =normalize_space= :
|
||||
% As =text=, but uses normalize_space/2 to normalise
|
||||
% white-space in the output
|
||||
% $ =number= :
|
||||
% Extract an integer or float from the value. Ignores
|
||||
% leading and trailing white-space
|
||||
% $ =|@|=Attribute :
|
||||
% Evaluates to the value of the given attribute. Attribute
|
||||
% can be a compound term. In this case the functor name
|
||||
% denotes the element and arguments perform transformations
|
||||
% on the attribute value. Defined transformations are:
|
||||
% - *`index(?Index)`*
|
||||
% True if the element is the Index-th child of its parent,
|
||||
% where 1 denotes the first child. Index can be one of:
|
||||
%
|
||||
% - number
|
||||
% Translate the value into a number using
|
||||
% xsd_number_chars/2.
|
||||
% - integer
|
||||
% As `number`, but subsequently transform the value
|
||||
% into an integer using the round/1 function.
|
||||
% - float
|
||||
% As `number`, but subsequently transform the value
|
||||
% into a float using the float/1 function.
|
||||
% - lower
|
||||
% Translate the value to lower case, preserving
|
||||
% the type.
|
||||
% - upper
|
||||
% Translate the value to upper case, preserving
|
||||
% the type.
|
||||
% - *`Var`*
|
||||
% `Var` is unified with the index of the matched element.
|
||||
% - *`last`*
|
||||
% True for the last element.
|
||||
% - *`last - IntExpr`*
|
||||
% True for the last-minus-nth element. For example,
|
||||
% `last-1` is the element directly preceding the last one.
|
||||
% - *`IntExpr`*
|
||||
% True for the element whose index equals `IntExpr`.
|
||||
% - *`Integer`*
|
||||
% The N-th element with the given name, with 1 denoting the
|
||||
% first element. Same as `index(Integer)`.
|
||||
% - *`last`*
|
||||
% The last element with the given name. Same as
|
||||
% `index(last)`.
|
||||
% - *`last - IntExpr`*
|
||||
% The IntExpr-th element before the last.
|
||||
% Same as `index(last-IntExpr)`.
|
||||
%
|
||||
% In addition, the argument-list can be _conditions_:
|
||||
% Defined function argument values are:
|
||||
%
|
||||
% $ Left = Right :
|
||||
% Succeeds if the left-hand unifies with the right-hand.
|
||||
% If the left-hand side is a function, this is evaluated.
|
||||
% The right-hand side is _never_ evaluated, and thus the
|
||||
% condition `content = content` defines that the content
|
||||
% of the element is the atom `content`.
|
||||
% The functions `lower_case` and `upper_case` can be applied
|
||||
% to Right (see example below).
|
||||
% $ contains(Haystack, Needle) :
|
||||
% Succeeds if Needle is a sub-list of Haystack.
|
||||
% $ XPath :
|
||||
% Succeeds if XPath matches in the currently selected
|
||||
% sub-DOM. For example, the following expression finds
|
||||
% an =h3= element inside a =div= element, where the =div=
|
||||
% element itself contains an =h2= child with a =strong=
|
||||
% child.
|
||||
% - *`self`*
|
||||
% Evaluate to the entire element
|
||||
% - *`content`*
|
||||
% Evaluate to the content of the element (a list)
|
||||
% - *`text`*
|
||||
% Evaluates to all text from the sub-tree, represented
|
||||
% as a list of characters.
|
||||
% - *`text(atom)`*
|
||||
% Evaluates to all text from the sub-tree as an atom.
|
||||
% - *`normalize_space`*
|
||||
% As `text`, but uses `normalize_space/2` to normalise
|
||||
% white-space in the output
|
||||
% - *`number`*
|
||||
% Extract an integer or float from the value. Ignores
|
||||
% leading and trailing white-space
|
||||
% - *`@Attribute`*
|
||||
% Evaluates to the value of the given attribute. Attribute
|
||||
% can be a compound term. In this case the functor name
|
||||
% denotes the element and arguments perform transformations
|
||||
% on the attribute value. Defined transformations are:
|
||||
%
|
||||
% ==
|
||||
% //div(h2/strong)/h3
|
||||
% ==
|
||||
% - *`number`*
|
||||
% Translate the value into a number using
|
||||
% `xsd_number_chars/2`.
|
||||
% - *`integer`*
|
||||
% As `number`, but subsequently transform the value
|
||||
% into an integer using the `round/1` function.
|
||||
% - *`float`*
|
||||
% As `number`, but subsequently transform the value
|
||||
% into a float using the `float/1` function.
|
||||
% - *`lower`*
|
||||
% Translate the value to lower case, preserving
|
||||
% the type.
|
||||
% - *`upper`*
|
||||
% Translate the value to upper case, preserving
|
||||
% the type.
|
||||
%
|
||||
% This is equivalent to the conjunction of XPath goals below.
|
||||
% In addition, the argument-list can be _conditions_:
|
||||
%
|
||||
% ==
|
||||
% ...,
|
||||
% xpath(DOM, //(div), Div),
|
||||
% xpath(Div, h2/strong, _),
|
||||
% xpath(Div, h3, Result)
|
||||
% ==
|
||||
% - *`Left = Right`*
|
||||
% Succeeds if the left-hand unifies with the right-hand.
|
||||
% If the left-hand side is a function, this is evaluated.
|
||||
% The right-hand side is _never_ evaluated, and thus the
|
||||
% condition `content = content` defines that the content
|
||||
% of the element is the atom `content`.
|
||||
% The functions `lower_case` and `upper_case` can be applied
|
||||
% to Right (see example below).
|
||||
% - *`contains(Haystack, Needle)`*
|
||||
% Succeeds if Needle is a sub-list of Haystack.
|
||||
% - *`XPath`*
|
||||
% Succeeds if XPath matches in the currently selected
|
||||
% sub-DOM. For example, the following expression finds
|
||||
% an `h3` element inside a `div` element, where the `div`
|
||||
% element itself contains an `h2` child with a `strong`
|
||||
% child.
|
||||
%
|
||||
% **Examples**:
|
||||
% ```
|
||||
% //div(h2/strong)/h3
|
||||
% ```
|
||||
%
|
||||
% Match each table-row in DOM:
|
||||
% This is equivalent to the conjunction of XPath goals below.
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //tr, TR)
|
||||
% ==
|
||||
% ```
|
||||
% ...,
|
||||
% xpath(DOM, //(div), Div),
|
||||
% xpath(Div, h2/strong, _),
|
||||
% xpath(Div, h3, Result)
|
||||
% ```
|
||||
%
|
||||
% Match the last cell of each tablerow in DOM. This example
|
||||
% illustrates that a result can be the input of subsequent xpath/3
|
||||
% queries. Using multiple queries on the intermediate TR term
|
||||
% guarantee that all results come from the same table-row:
|
||||
% #### Examples
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, /td(last), TD)
|
||||
% ==
|
||||
% Match each table-row in DOM:
|
||||
%
|
||||
% Match each =href= attribute in an <a> element
|
||||
% ```
|
||||
% xpath(DOM, //tr, TR)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //a(@href), HREF)
|
||||
% ==
|
||||
% Match the last cell of each tablerow in DOM. This example
|
||||
% illustrates that a result can be the input of subsequent `xpath/3`
|
||||
% queries. Using multiple queries on the intermediate TR term
|
||||
% guarantee that all results come from the same table-row:
|
||||
%
|
||||
% Suppose we have a table containing rows where each first column
|
||||
% is the name of a product with a link to details and the second
|
||||
% is the price (a number). The following predicate matches the
|
||||
% name, URL and price:
|
||||
% ```
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, /td(last), TD)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% product(DOM, Name, URL, Price) :-
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, td(1), C1),
|
||||
% xpath(C1, /self(normalize_space), Name),
|
||||
% xpath(C1, a(@href), URL),
|
||||
% xpath(TR, td(2, number), Price).
|
||||
% ==
|
||||
% Match each `href` attribute in an `<a>` element
|
||||
%
|
||||
% Suppose we want to select books with genre="thriller" from a
|
||||
% tree containing elements =|<book genre=...>|=
|
||||
% ```
|
||||
% xpath(DOM, //a(@href), HREF)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% thriller(DOM, Book) :-
|
||||
% xpath(DOM, //book(@genre=thiller), Book).
|
||||
% ==
|
||||
% Suppose we have a table containing rows where each first column
|
||||
% is the name of a product with a link to details and the second
|
||||
% is the price (a number). The following predicate matches the
|
||||
% name, URL and price:
|
||||
%
|
||||
% Match the elements =|<table align="center">|= _and_ =|<table
|
||||
% align="CENTER">|=:
|
||||
% ```
|
||||
% product(DOM, Name, URL, Price) :-
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, td(1), C1),
|
||||
% xpath(C1, /self(normalize_space), Name),
|
||||
% xpath(C1, a(@href), URL),
|
||||
% xpath(TR, td(2, number), Price).
|
||||
% ```
|
||||
%
|
||||
% ```prolog
|
||||
% //table(@align(lower) = center)
|
||||
% ```
|
||||
% Suppose we want to select books with genre="thriller" from a
|
||||
% tree containing elements `<book genre=...>`
|
||||
%
|
||||
% Get the `width` and `height` of a `div` element as a number,
|
||||
% and the `div` node itself:
|
||||
% ```
|
||||
% thriller(DOM, Book) :-
|
||||
% xpath(DOM, //book(@genre=thiller), Book).
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //div(@width(number)=W, @height(number)=H), Div)
|
||||
% ==
|
||||
% Match the elements `<table align="center">` _and_ `<table
|
||||
% align="CENTER">`:
|
||||
%
|
||||
% Note that `div` is an infix operator, so parentheses must be
|
||||
% used in cases like the following:
|
||||
% ```
|
||||
% //table(@align(lower) = center)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //(div), Div)
|
||||
% ==
|
||||
% Get the `width` and `height` of a `div` element as a number,
|
||||
% and the `div` node itself:
|
||||
%
|
||||
% ```
|
||||
% xpath(DOM, //div(@width(number)=W, @height(number)=H), Div)
|
||||
% ```
|
||||
%
|
||||
% Note that `div` is an infix operator, so parentheses must be
|
||||
% used in cases like the following:
|
||||
%
|
||||
% ```
|
||||
% xpath(DOM, //(div), Div)
|
||||
% ```
|
||||
|
||||
xpath(DOM, Spec, Content) :-
|
||||
in_dom(Spec, DOM, Content).
|
||||
|
||||
+445
-826
File diff suppressed because it is too large
Load Diff
+172
-54
@@ -1,3 +1,6 @@
|
||||
use dashu::base::Abs;
|
||||
use dashu::base::Gcd;
|
||||
use dashu::integer::IBig;
|
||||
use divrem::*;
|
||||
|
||||
use crate::arena::*;
|
||||
@@ -8,7 +11,7 @@ use crate::heap_iter::*;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use crate::types::*;
|
||||
|
||||
use crate::fixnum;
|
||||
@@ -159,7 +162,7 @@ pub(crate) fn add(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(add_f(float_fn_to_f(n1.get_num())?, n2)?))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1) + &*n2, arena)) // add_i
|
||||
Ok(Number::arena_from(&*n1 + &*n2, arena)) // add_i
|
||||
}
|
||||
(Number::Integer(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Integer(n1)) => {
|
||||
@@ -167,7 +170,7 @@ pub(crate) fn add(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
}
|
||||
(Number::Integer(n1), Number::Rational(n2))
|
||||
| (Number::Rational(n2), Number::Integer(n1)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*n1) + &*n2, arena))
|
||||
Ok(Number::arena_from(&*n1 + &*n2, arena))
|
||||
}
|
||||
(Number::Rational(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Rational(n1)) => {
|
||||
@@ -177,7 +180,7 @@ pub(crate) fn add(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(add_f(f1, f2)?))
|
||||
}
|
||||
(Number::Rational(r1), Number::Rational(r2)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*r1) + &*r2, arena))
|
||||
Ok(Number::arena_from(&*r1 + &*r2, arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,9 +194,15 @@ pub(crate) fn neg(n: Number, arena: &mut Arena) -> Number {
|
||||
Number::arena_from(-Integer::from(n.get_num()), arena)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => Number::arena_from(-Integer::from(&*n), arena),
|
||||
Number::Integer(n) => {
|
||||
let n_clone: Integer = (*n).clone();
|
||||
Number::arena_from(-Integer::from(n_clone), arena)
|
||||
},
|
||||
Number::Float(OrderedFloat(f)) => Number::Float(OrderedFloat(-f)),
|
||||
Number::Rational(r) => Number::arena_from(-Rational::from(&*r), arena),
|
||||
Number::Rational(r) => {
|
||||
let r_clone: Rational = (*r).clone();
|
||||
Number::arena_from(-Rational::from(r_clone), arena)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,12 +212,19 @@ pub(crate) fn abs(n: Number, arena: &mut Arena) -> Number {
|
||||
if let Some(n) = n.get_num().checked_abs() {
|
||||
fixnum!(Number, n, arena)
|
||||
} else {
|
||||
Number::arena_from(Integer::from(n.get_num()).abs(), arena)
|
||||
let arena_int = Integer::from(n.get_num());
|
||||
Number::arena_from(arena_int.abs(), arena)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => Number::arena_from(Integer::from(n.abs_ref()), arena),
|
||||
Number::Integer(n) => {
|
||||
let n_clone: Integer = (*n).clone();
|
||||
Number::arena_from(Integer::from(n_clone.abs()), arena)
|
||||
},
|
||||
Number::Float(f) => Number::Float(f.abs()),
|
||||
Number::Rational(r) => Number::arena_from(Rational::from(r.abs_ref()), arena),
|
||||
Number::Rational(r) => {
|
||||
let r_clone: Rational = (*r).clone();
|
||||
Number::arena_from(Rational::from(r_clone.abs()), arena)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +263,8 @@ pub(crate) fn mul(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(mul_f(float_fn_to_f(n1.get_num())?, n2)?))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1) * &*n2, arena)) // mul_i
|
||||
let n1_clone: Integer = (*n1).clone();
|
||||
Ok(Number::arena_from(Integer::from(n1_clone) * &*n2, arena)) // mul_i
|
||||
}
|
||||
(Number::Integer(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Integer(n1)) => {
|
||||
@@ -255,7 +272,8 @@ pub(crate) fn mul(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
}
|
||||
(Number::Integer(n1), Number::Rational(n2))
|
||||
| (Number::Rational(n2), Number::Integer(n1)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*n1) * &*n2, arena))
|
||||
let n1_clone: Integer = (*n1).clone();
|
||||
Ok(Number::arena_from(Rational::from(n1_clone) * &*n2, arena))
|
||||
}
|
||||
(Number::Rational(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Rational(n1)) => {
|
||||
@@ -265,7 +283,8 @@ pub(crate) fn mul(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(mul_f(f1, f2)?))
|
||||
}
|
||||
(Number::Rational(r1), Number::Rational(r2)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*r1) * &*r2, arena))
|
||||
let r1_clone: Rational = (*r1).clone();
|
||||
Ok(Number::arena_from(Rational::from(r1_clone) * &*r2, arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,7 +357,7 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
|
||||
if !(n1_i == 1 || n1_i == 0 || n1_i == -1) && &*n2 < &0 {
|
||||
if !(n1_i == 1 || n1_i == 0 || n1_i == -1) && &*n2 < &Integer::from(0) {
|
||||
let n = Number::Fixnum(n1);
|
||||
Err(numerical_type_error(ValidType::Float, n, stub_gen))
|
||||
} else {
|
||||
@@ -349,7 +368,7 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => {
|
||||
let n2_i = n2.get_num();
|
||||
|
||||
if !(&*n1 == &1 || &*n1 == &0 || &*n1 == &-1) && n2_i < 0 {
|
||||
if !(&*n1 == &Integer::from(1) || &*n1 == &Integer::from(0) || &*n1 == &Integer::from(-1)) && n2_i < 0 {
|
||||
let n = Number::Integer(n1);
|
||||
Err(numerical_type_error(ValidType::Float, n, stub_gen))
|
||||
} else {
|
||||
@@ -358,7 +377,7 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if !(&*n1 == &1 || &*n1 == &0 || &*n1 == &-1) && &*n2 < &0 {
|
||||
if !(&*n1 == &Integer::from(1) || &*n1 == &Integer::from(0) || &*n1 == &Integer::from(-1)) && &*n2 < &Integer::from(0) {
|
||||
let n = Number::Integer(n1);
|
||||
Err(numerical_type_error(ValidType::Float, n, stub_gen))
|
||||
} else {
|
||||
@@ -521,7 +540,7 @@ pub fn rational_from_number(
|
||||
match n {
|
||||
Number::Fixnum(n) => Ok(arena_alloc!(Rational::from(n.get_num()), arena)),
|
||||
Number::Rational(r) => Ok(r),
|
||||
Number::Float(OrderedFloat(f)) => match Rational::from_f64(f) {
|
||||
Number::Float(OrderedFloat(f)) => match Rational::simplest_from_f64(f) {
|
||||
Some(r) => Ok(arena_alloc!(r, arena)),
|
||||
None => Err(Box::new(move |machine_st| {
|
||||
let instantiation_error = machine_st.instantiation_error();
|
||||
@@ -530,7 +549,10 @@ pub fn rational_from_number(
|
||||
machine_st.error_form(instantiation_error, stub)
|
||||
})),
|
||||
},
|
||||
Number::Integer(n) => Ok(arena_alloc!(Rational::from(&*n), arena)),
|
||||
Number::Integer(n) => {
|
||||
let n_clone: Integer = (*n).clone();
|
||||
Ok(arena_alloc!(Rational::from(n_clone), arena))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +612,7 @@ pub(crate) fn idiv(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number,
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(
|
||||
<(Integer, Integer)>::from(n1.div_rem_ref(&*n2)).0,
|
||||
<(Integer, Integer)>::from(n1.div_rem_floor_ref(&*n2)).0,
|
||||
arena,
|
||||
))
|
||||
}
|
||||
@@ -624,6 +646,10 @@ pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
functor_stub(shr_atom, 2)
|
||||
};
|
||||
|
||||
if n2.is_integer() && n2.is_negative() {
|
||||
return shl(n1, neg(n2, arena), arena);
|
||||
}
|
||||
|
||||
match (n1, n2) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
@@ -631,33 +657,33 @@ pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
|
||||
let n1 = Integer::from(n1_i);
|
||||
|
||||
if let Ok(n2) = u32::try_from(n2_i) {
|
||||
if let Ok(n2) = usize::try_from(n2_i) {
|
||||
return Ok(Number::arena_from(n1 >> n2, arena));
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 >> u32::max_value(), arena));
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 >> usize::max_value(), arena));
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
|
||||
match n2.to_u32() {
|
||||
match n2.to_usize() {
|
||||
Some(n2) => Ok(Number::arena_from(n1 >> n2, arena)),
|
||||
_ => Ok(Number::arena_from(n1 >> u32::max_value(), arena)),
|
||||
_ => {
|
||||
Ok(Number::arena_from(n1 >> usize::max_value(), arena))
|
||||
},
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match u32::try_from(n2.get_num()) {
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 >> u32::max_value()),
|
||||
arena,
|
||||
)),
|
||||
_ => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 >> usize::max_value()),arena))
|
||||
},
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match n2.to_u32() {
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match n2.to_usize() {
|
||||
Some(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 >> u32::max_value()),
|
||||
arena,
|
||||
)),
|
||||
_ => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 >> usize::max_value()), arena))
|
||||
},
|
||||
},
|
||||
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
@@ -667,10 +693,14 @@ pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
|
||||
pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
let stub_gen = || {
|
||||
let shl_atom = atom!(">>");
|
||||
let shl_atom = atom!("<<");
|
||||
functor_stub(shl_atom, 2)
|
||||
};
|
||||
|
||||
if n2.is_integer() && n2.is_negative() {
|
||||
return shr(n1, neg(n2, arena), arena);
|
||||
}
|
||||
|
||||
match (n1, n2) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
@@ -678,33 +708,33 @@ pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
|
||||
let n1 = Integer::from(n1_i);
|
||||
|
||||
if let Ok(n2) = u32::try_from(n2_i) {
|
||||
if let Ok(n2) = usize::try_from(n2_i) {
|
||||
return Ok(Number::arena_from(n1 << n2, arena));
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 << u32::max_value(), arena));
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 << usize::max_value(), arena));
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
|
||||
match n2.to_u32() {
|
||||
Some(n2) => Ok(Number::arena_from(n1 << n2, arena)),
|
||||
_ => Ok(Number::arena_from(n1 << u32::max_value(), arena)),
|
||||
Some(n2) => Ok(Number::arena_from(n1.to_u64().unwrap() << n2, arena)),
|
||||
_ => {
|
||||
Ok(Number::arena_from(n1 << usize::max_value(), arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match u32::try_from(n2.get_num()) {
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 << u32::max_value()),
|
||||
arena,
|
||||
)),
|
||||
_ => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 << usize::max_value()),arena))
|
||||
}
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match n2.to_u32() {
|
||||
Some(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 << u32::max_value()),
|
||||
arena,
|
||||
)),
|
||||
Some(n2) => Ok(Number::arena_from(Integer::from(n1.to_u64().unwrap() << n2), arena)),
|
||||
_ => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 << usize::max_value()),arena))
|
||||
}
|
||||
},
|
||||
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
@@ -918,18 +948,21 @@ pub(crate) fn gcd(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
if let Some(result) = isize_gcd(n1_i, n2_i) {
|
||||
Ok(Number::arena_from(result, arena))
|
||||
} else {
|
||||
let value: IBig = Integer::from(n1_i).gcd(&Integer::from(n2_i)).into();
|
||||
Ok(Number::arena_from(
|
||||
Integer::from(n1_i).gcd(&Integer::from(n2_i)),
|
||||
value,
|
||||
arena,
|
||||
))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) | (Number::Integer(n2), Number::Fixnum(n1)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
Ok(Number::arena_from(Integer::from(n2.gcd_ref(&n1)), arena))
|
||||
let n2_clone: Integer = (*n2).clone();
|
||||
Ok(Number::arena_from(Integer::from(n2_clone.gcd(&n1)), arena))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
Ok(Number::arena_from(Integer::from(n1.gcd_ref(&n2)), arena))
|
||||
let n1_clone: Integer = (*n1).clone();
|
||||
Ok(Number::arena_from(Integer::from(n1_clone.gcd(&Integer::from(n2.to_isize().unwrap()))) as IBig, arena))
|
||||
}
|
||||
(Number::Float(f), _) | (_, Number::Float(f)) => {
|
||||
let n = Number::Float(f);
|
||||
@@ -998,6 +1031,63 @@ pub(crate) fn atan(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.atan())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn asinh(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.asinh())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn acosh(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.acosh())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn atanh(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
let stub_gen = || {
|
||||
let is_atom = atom!("is");
|
||||
functor_stub(is_atom, 2)
|
||||
};
|
||||
|
||||
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
|
||||
|
||||
try_numeric_result!(if f1 == 1.0 || f1 == -1.0 {
|
||||
Err(EvalError::Undefined)
|
||||
} else {
|
||||
result_f(&Number::Float(OrderedFloat(f1.atanh())))
|
||||
},
|
||||
stub_gen)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn sinh(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.sinh())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cosh(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.cosh())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn tanh(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.tanh())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn log10(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.log(10f64))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_fractional_part(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.fract())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_integer_part(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.trunc())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn sqrt(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
if n1.is_negative() {
|
||||
@@ -1017,6 +1107,7 @@ pub(crate) fn floor(n1: Number, arena: &mut Arena) -> Number {
|
||||
rnd_i(&n1, arena)
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn ceiling(n1: Number, arena: &mut Arena) -> Number {
|
||||
let n1 = neg(n1, arena);
|
||||
@@ -1098,7 +1189,7 @@ impl MachineState {
|
||||
|
||||
pub(crate) fn arith_eval_by_metacall(&mut self, value: HeapCellValue) -> Result<Number, MachineStub> {
|
||||
let stub_gen = || functor_stub(atom!("is"), 2);
|
||||
let mut iter = stackful_post_order_iter(&mut self.heap, value);
|
||||
let mut iter = stackful_post_order_iter(&mut self.heap, &mut self.stack, value);
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
if value.get_forwarding_bit() {
|
||||
@@ -1115,7 +1206,7 @@ impl MachineState {
|
||||
HeapCellValueTag::PStrLoc) => {
|
||||
(atom!("."), 2)
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var) => {
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var | HeapCellValueTag::StackVar) => {
|
||||
let err = self.instantiation_error();
|
||||
return Err(self.error_form(err, stub_gen()));
|
||||
}
|
||||
@@ -1247,6 +1338,33 @@ impl MachineState {
|
||||
atom!("tan") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, tan(a1))
|
||||
))),
|
||||
atom!("cosh") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, cosh(a1))
|
||||
))),
|
||||
atom!("sinh") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, sinh(a1))
|
||||
))),
|
||||
atom!("tanh") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, tanh(a1))
|
||||
))),
|
||||
atom!("acosh") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, acosh(a1))
|
||||
))),
|
||||
atom!("asinh") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, asinh(a1))
|
||||
))),
|
||||
atom!("atanh") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, atanh(a1))
|
||||
))),
|
||||
atom!("log10") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, log10(a1))
|
||||
))),
|
||||
atom!("float_fractional_part") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, float_fractional_part(a1))
|
||||
))),
|
||||
atom!("float_integer_part") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, float_integer_part(a1))
|
||||
))),
|
||||
atom!("sqrt") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, sqrt(a1))
|
||||
))),
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
:- module('$atts', []).
|
||||
|
||||
|
||||
driver(Vars, Values) :-
|
||||
iterate(Vars, Values, ListOfListsOfGoalLists),
|
||||
!,
|
||||
call_goals(ListOfListsOfGoalLists),
|
||||
'$reset_attr_var_state',
|
||||
'$return_from_verify_attr'.
|
||||
|
||||
iterate([Var|VarBindings], [Value|ValueBindings], [ListOfGoalLists | ListsCubed]) :-
|
||||
|
||||
@@ -33,8 +33,8 @@ impl AttrVarInitializer {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn reset(&mut self) {
|
||||
self.attr_var_queue.clear();
|
||||
pub(super) fn reset(&mut self, len: usize) {
|
||||
self.attr_var_queue.truncate(len);
|
||||
self.bindings.clear();
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ impl MachineState {
|
||||
self.cp = INSTALL_VERIFY_ATTR_INTERRUPT;
|
||||
}
|
||||
|
||||
debug_assert_eq!(self.heap[h].get_tag(), HeapCellValueTag::AttrVar);
|
||||
self.attr_var_init.bindings.push((h, addr));
|
||||
}
|
||||
|
||||
@@ -63,10 +64,9 @@ impl MachineState {
|
||||
.map(|(ref h, _)| attr_var_as_cell!(*h));
|
||||
|
||||
let var_list_addr = heap_loc_as_cell!(iter_to_heap_list(&mut self.heap, iter));
|
||||
|
||||
let iter = self.attr_var_init.bindings.drain(0..).map(|(_, ref v)| *v);
|
||||
|
||||
let value_list_addr = heap_loc_as_cell!(iter_to_heap_list(&mut self.heap, iter));
|
||||
|
||||
(var_list_addr, value_list_addr)
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ impl MachineState {
|
||||
let mut seen_set = IndexSet::new();
|
||||
let mut seen_vars = vec![];
|
||||
|
||||
let mut iter = stackful_preorder_iter(&mut self.heap, cell);
|
||||
let mut iter = stackful_preorder_iter(&mut self.heap, &mut self.stack, cell);
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
read_heap_cell!(value,
|
||||
@@ -147,6 +147,16 @@ impl MachineState {
|
||||
|
||||
let value = unmark_cell_bits!(value);
|
||||
|
||||
if h != iter.focus().value() as usize {
|
||||
let deref_value = heap_bound_store(iter.heap, heap_bound_deref(iter.heap, value));
|
||||
|
||||
if deref_value.is_compound(iter.heap) {
|
||||
// a cyclic structure is bound to the attributed variable at h.
|
||||
// it mustn't be included in seen_vars.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
seen_vars.push(value);
|
||||
seen_set.insert(h);
|
||||
|
||||
@@ -157,7 +167,7 @@ impl MachineState {
|
||||
loop {
|
||||
read_heap_cell!(iter.heap[l],
|
||||
(HeapCellValueTag::Lis) => {
|
||||
iter.push_stack(l);
|
||||
iter.push_stack(IterStackLoc::iterable_loc(l, HeapOrStackTag::Heap));
|
||||
// l = elem + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::instructions::*;
|
||||
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
fn capture_offset(line: &Instruction, index: usize, stack: &mut Vec<usize>) -> bool {
|
||||
@@ -7,38 +8,24 @@ fn capture_offset(line: &Instruction, index: usize, stack: &mut Vec<usize>) -> b
|
||||
&Instruction::TryMeElse(offset) if offset > 0 => {
|
||||
stack.push(index + offset);
|
||||
}
|
||||
&Instruction::DefaultRetryMeElse(offset) |
|
||||
&Instruction::RetryMeElse(offset)
|
||||
if offset > 0 =>
|
||||
{
|
||||
&Instruction::DefaultRetryMeElse(offset) | &Instruction::RetryMeElse(offset) if offset > 0 => {
|
||||
stack.push(index + offset);
|
||||
}
|
||||
&Instruction::DynamicElse(_, _, NextOrFail::Next(offset))
|
||||
if offset > 0 =>
|
||||
{
|
||||
&Instruction::DynamicElse(_, _, NextOrFail::Next(offset)) if offset > 0 => {
|
||||
stack.push(index + offset);
|
||||
}
|
||||
&Instruction::DynamicInternalElse(_, _, NextOrFail::Next(offset))
|
||||
if offset > 0 =>
|
||||
{
|
||||
&Instruction::DynamicInternalElse(_, _, NextOrFail::Next(offset)) if offset > 0 => {
|
||||
stack.push(index + offset);
|
||||
}
|
||||
&Instruction::JmpByCall(_, offset, _) => {
|
||||
stack.push(index + offset);
|
||||
}
|
||||
&Instruction::JmpByExecute(_, offset, _) => {
|
||||
stack.push(index + offset);
|
||||
return true;
|
||||
}
|
||||
&Instruction::Proceed => {
|
||||
&Instruction::Proceed | &Instruction::JmpByCall(_) => {
|
||||
return true;
|
||||
}
|
||||
&Instruction::RevJmpBy(offset) => {
|
||||
if offset > 0 {
|
||||
stack.push(index - offset);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
instr if instr.is_execute() => {
|
||||
return true;
|
||||
@@ -55,7 +42,7 @@ fn capture_offset(line: &Instruction, index: usize, stack: &mut Vec<usize>) -> b
|
||||
*/
|
||||
pub(crate) fn walk_code(code: &Code, p: usize, mut walker: impl FnMut(&Instruction)) {
|
||||
let mut stack = vec![p];
|
||||
let mut visited_indices = IndexSet::new();
|
||||
let mut visited_indices = IndexSet::with_hasher(FxBuildHasher::default());
|
||||
|
||||
while let Some(first_index) = stack.pop() {
|
||||
if visited_indices.contains(&first_index) {
|
||||
@@ -73,23 +60,3 @@ pub(crate) fn walk_code(code: &Code, p: usize, mut walker: impl FnMut(&Instructi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* A function for code walking that might result in modification to
|
||||
* the code. Otherwise identical to walk_code.
|
||||
*/
|
||||
/*
|
||||
pub(crate) fn walk_code_mut(code: &mut Code, p: usize, mut walker: impl FnMut(&mut Line))
|
||||
{
|
||||
let mut queue = VecDeque::from(vec![p]);
|
||||
|
||||
while let Some(first_idx) = queue.pop_front() {
|
||||
let mut last_idx = first_idx;
|
||||
|
||||
capture_next_range(code, &mut queue, &mut last_idx);
|
||||
|
||||
for instr in &mut code[first_idx .. last_idx + 1] {
|
||||
walker(instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
+41
-97
@@ -44,60 +44,6 @@ pub(super) fn bootstrapping_compile(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// throw errors if declaration or query found.
|
||||
pub(super) fn compile_relation(
|
||||
cg: &mut CodeGenerator,
|
||||
tl: &TopLevel,
|
||||
) -> Result<Code, CompilationError> {
|
||||
match tl {
|
||||
&TopLevel::Query(_) => Err(CompilationError::ExpectedRel),
|
||||
&TopLevel::Predicate(ref clauses) => cg.compile_predicate(&clauses),
|
||||
&TopLevel::Fact(ref fact, ..) => cg.compile_fact(fact),
|
||||
&TopLevel::Rule(ref rule, ..) => cg.compile_rule(rule),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compile_appendix(
|
||||
code: &mut Code,
|
||||
mut queue: VecDeque<TopLevel>,
|
||||
jmp_by_locs: Vec<usize>,
|
||||
non_counted_bt: bool,
|
||||
atom_tbl: &mut AtomTable,
|
||||
) -> Result<(), CompilationError> {
|
||||
let mut jmp_by_locs = VecDeque::from(jmp_by_locs);
|
||||
|
||||
while let Some(jmp_by_offset) = jmp_by_locs.pop_front() {
|
||||
let code_len = code.len();
|
||||
|
||||
match &mut code[jmp_by_offset] {
|
||||
&mut Instruction::JmpByCall(_, ref mut offset, ..) |
|
||||
&mut Instruction::JmpByExecute(_, ref mut offset, ..) => {
|
||||
*offset = code_len - jmp_by_offset;
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
// false because the inner predicate is a one-off, hence not extensible.
|
||||
let settings = CodeGenSettings {
|
||||
global_clock_tick: None,
|
||||
is_extensible: false,
|
||||
non_counted_bt,
|
||||
};
|
||||
|
||||
let mut cg = CodeGenerator::new(atom_tbl, settings);
|
||||
|
||||
let tl = queue.pop_front().unwrap();
|
||||
let decl_code = compile_relation(&mut cg, &tl)?;
|
||||
|
||||
jmp_by_locs.extend(cg.jmp_by_locs.into_iter().map(|offset| offset + code.len()));
|
||||
code.extend(decl_code.into_iter());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lower_bound_of_target_clause(skeleton: &PredicateSkeleton, target_pos: usize) -> usize {
|
||||
if target_pos == 0 {
|
||||
return 0;
|
||||
@@ -1342,22 +1288,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
let mut preprocessor = Preprocessor::new(settings);
|
||||
|
||||
let clause = self.try_term_to_tl(term, &mut preprocessor)?;
|
||||
let queue = preprocessor.parse_queue(self)?;
|
||||
// let queue = preprocessor.parse_queue(self)?;
|
||||
|
||||
let mut cg = CodeGenerator::new(
|
||||
&mut LS::machine_st(&mut self.payload).atom_tbl,
|
||||
settings,
|
||||
);
|
||||
|
||||
let mut clause_code = cg.compile_predicate(&vec![clause])?;
|
||||
|
||||
compile_appendix(
|
||||
&mut clause_code,
|
||||
queue,
|
||||
cg.jmp_by_locs,
|
||||
settings.non_counted_bt,
|
||||
cg.atom_tbl,
|
||||
)?;
|
||||
let clause_code = cg.compile_predicate(vec![clause])?;
|
||||
|
||||
Ok(StandaloneCompileResult {
|
||||
clause_code,
|
||||
@@ -1385,22 +1323,12 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
clauses.push(self.try_term_to_tl(term, &mut preprocessor)?);
|
||||
}
|
||||
|
||||
let queue = preprocessor.parse_queue(self)?;
|
||||
|
||||
let mut cg = CodeGenerator::new(
|
||||
&mut LS::machine_st(&mut self.payload).atom_tbl,
|
||||
settings,
|
||||
);
|
||||
|
||||
let mut code = cg.compile_predicate(&clauses)?;
|
||||
|
||||
compile_appendix(
|
||||
&mut code,
|
||||
queue,
|
||||
cg.jmp_by_locs,
|
||||
settings.non_counted_bt,
|
||||
cg.atom_tbl,
|
||||
)?;
|
||||
let mut code = cg.compile_predicate(clauses)?;
|
||||
|
||||
if settings.is_extensible {
|
||||
let mut clause_clause_locs = VecDeque::new();
|
||||
@@ -1869,7 +1797,13 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
skeleton.clauses[target_pos + 1].clause_start =
|
||||
skeleton.clauses[target_pos].clause_start;
|
||||
|
||||
let index_ptr_opt = if target_pos == 0 {
|
||||
let update_code_index = target_pos == 0 &&
|
||||
skeleton.clauses[target_pos + 1]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc()
|
||||
.is_none();
|
||||
|
||||
let index_ptr_opt = if update_code_index {
|
||||
Some(IndexPtr::index(clause_loc))
|
||||
} else {
|
||||
None
|
||||
@@ -2274,14 +2208,17 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.ok_or(SessionError::NamelessEntry)?;
|
||||
|
||||
let listing_src_file_name = self.listing_src_file_name();
|
||||
let payload_compilation_target = self.payload.compilation_target;
|
||||
|
||||
let mut predicate_info = self
|
||||
.wam_prelude
|
||||
.indices
|
||||
.get_predicate_skeleton(&self.payload.predicates.compilation_target, &key)
|
||||
.map(|skeleton| skeleton.predicate_info())
|
||||
.unwrap_or_default();
|
||||
// payload_compilation_target describes the compilation context,
|
||||
// e.g. compiling
|
||||
//
|
||||
// table_wrapper:tabled(get_node(A), b).
|
||||
//
|
||||
// without a module declaration means self.payload.compilation_target
|
||||
// is CompilationTarget::User while self.payload.predicates.compilation_target
|
||||
// is CompilationTarget::Module(atom!("table_wrapper")).
|
||||
|
||||
let payload_compilation_target = self.payload.compilation_target;
|
||||
|
||||
let local_predicate_info = self
|
||||
.wam_prelude
|
||||
@@ -2295,34 +2232,37 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.map(|skeleton| skeleton.predicate_info())
|
||||
.unwrap_or_default();
|
||||
|
||||
if local_predicate_info.must_retract_local_clauses() {
|
||||
let mut predicate_info = self
|
||||
.wam_prelude
|
||||
.indices
|
||||
.get_predicate_skeleton(&self.payload.predicates.compilation_target, &key)
|
||||
.map(|skeleton| skeleton.predicate_info())
|
||||
.unwrap_or_default();
|
||||
|
||||
let is_cross_module_clause =
|
||||
payload_compilation_target != self.payload.predicates.compilation_target;
|
||||
|
||||
if local_predicate_info.must_retract_local_clauses(is_cross_module_clause) {
|
||||
self.retract_local_clauses(&key, predicate_info.is_dynamic);
|
||||
}
|
||||
|
||||
let do_incremental_compile =
|
||||
if payload_compilation_target == self.payload.predicates.compilation_target {
|
||||
predicate_info.compile_incrementally()
|
||||
} else {
|
||||
local_predicate_info.is_multifile && predicate_info.compile_incrementally()
|
||||
};
|
||||
|
||||
let predicates_len = self.payload.predicates.len();
|
||||
let non_counted_bt = self.payload.non_counted_bt_preds.contains(&key);
|
||||
|
||||
if do_incremental_compile {
|
||||
if predicate_info.compile_incrementally() {
|
||||
let predicates = self.payload.predicates.take();
|
||||
|
||||
for term in predicates.predicates {
|
||||
self.incremental_compile_clause(
|
||||
key,
|
||||
term,
|
||||
payload_compilation_target,
|
||||
self.payload.predicates.compilation_target,
|
||||
non_counted_bt,
|
||||
AppendOrPrepend::Append,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
if payload_compilation_target != self.payload.predicates.compilation_target {
|
||||
if is_cross_module_clause {
|
||||
if !local_predicate_info.is_extensible {
|
||||
if predicate_info.is_multifile {
|
||||
println!(
|
||||
@@ -2337,9 +2277,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.indices
|
||||
.remove_predicate_skeleton(&self.payload.predicates.compilation_target, &key)
|
||||
{
|
||||
let compilation_target = self.payload.predicates.compilation_target;
|
||||
|
||||
if predicate_info.is_dynamic {
|
||||
let clause_clause_compilation_target =
|
||||
match self.payload.predicates.compilation_target {
|
||||
match compilation_target {
|
||||
CompilationTarget::User => {
|
||||
CompilationTarget::Module(atom!("builtins"))
|
||||
}
|
||||
@@ -2358,7 +2300,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
|
||||
self.payload.retraction_info.push_record(
|
||||
RetractionRecord::RemovedSkeleton(
|
||||
payload_compilation_target,
|
||||
compilation_target,
|
||||
key,
|
||||
skeleton,
|
||||
),
|
||||
@@ -2409,9 +2351,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.clause_clauses.drain(0..std::cmp::min(predicates_len, clause_clauses_len))
|
||||
.collect();
|
||||
|
||||
let compilation_target = self.payload.predicates.compilation_target;
|
||||
|
||||
self.compile_clause_clauses(
|
||||
key,
|
||||
payload_compilation_target,
|
||||
compilation_target,
|
||||
clauses_vec.into_iter(),
|
||||
AppendOrPrepend::Append,
|
||||
)?;
|
||||
|
||||
+60
-10
@@ -28,7 +28,10 @@ pub(crate) fn copy_term<T: CopierTarget>(
|
||||
attr_var_policy: AttrVarPolicy,
|
||||
) {
|
||||
let mut copy_term_state = CopyTermState::new(target, attr_var_policy);
|
||||
|
||||
copy_term_state.copy_term_impl(addr);
|
||||
copy_term_state.copy_attr_var_lists();
|
||||
copy_term_state.unwind_trail();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -38,6 +41,7 @@ struct CopyTermState<T: CopierTarget> {
|
||||
old_h: usize,
|
||||
target: T,
|
||||
attr_var_policy: AttrVarPolicy,
|
||||
attr_var_list_locs: Vec<(usize, HeapCellValue)>,
|
||||
}
|
||||
|
||||
impl<T: CopierTarget> CopyTermState<T> {
|
||||
@@ -48,6 +52,7 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
old_h: target.threshold(),
|
||||
target,
|
||||
attr_var_policy,
|
||||
attr_var_list_locs: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,16 +91,12 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
self.target.push(hcv);
|
||||
}
|
||||
|
||||
let cdr = self
|
||||
.target
|
||||
.store(self.target.deref(heap_loc_as_cell!(addr + 1)));
|
||||
let cdr = self.target.store(self.target.deref(heap_loc_as_cell!(addr + 1)));
|
||||
|
||||
if !cdr.is_var() {
|
||||
self.trail_list_cell(addr + 1, threshold);
|
||||
} else {
|
||||
let car = self
|
||||
.target
|
||||
.store(self.target.deref(heap_loc_as_cell!(addr)));
|
||||
let car = self.target.store(self.target.deref(heap_loc_as_cell!(addr)));
|
||||
|
||||
if !car.is_var() {
|
||||
self.trail_list_cell(addr, threshold);
|
||||
@@ -167,6 +168,51 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
self.trail.push((Ref::heap_cell(pstr_loc), trail_item));
|
||||
}
|
||||
|
||||
fn copy_attr_var_lists(&mut self) {
|
||||
while !self.attr_var_list_locs.is_empty() {
|
||||
let iter = mem::replace(&mut self.attr_var_list_locs, vec![]);
|
||||
|
||||
for (threshold, list_loc) in iter {
|
||||
self.target[threshold] = list_loc_as_cell!(self.target.threshold());
|
||||
self.copy_attr_var_list(list_loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Attributed variable attribute lists adhere to a particular
|
||||
* structure which is ensured by this function and not at all by
|
||||
* the vanilla copier.
|
||||
*/
|
||||
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) {
|
||||
while let HeapCellValueTag::Lis = list_addr.get_tag() {
|
||||
let threshold = self.target.threshold();
|
||||
let heap_loc = list_addr.get_value();
|
||||
let str_loc = self.target[heap_loc].get_value();
|
||||
|
||||
self.target.push(heap_loc_as_cell!(threshold+2));
|
||||
self.target.push(heap_loc_as_cell!(threshold+1));
|
||||
|
||||
read_heap_cell!(self.target[str_loc],
|
||||
(HeapCellValueTag::Atom) => {
|
||||
self.target.push(self.target[str_loc]);
|
||||
}
|
||||
(HeapCellValueTag::Str) => {
|
||||
self.copy_term_impl(self.target[str_loc]);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
);
|
||||
|
||||
list_addr = self.target[heap_loc + 1];
|
||||
|
||||
if HeapCellValueTag::Lis == list_addr.get_tag() {
|
||||
self.target[threshold + 1] = list_loc_as_cell!(self.target.threshold());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reinstantiate_var(&mut self, addr: HeapCellValue, frontier: usize) {
|
||||
read_heap_cell!(addr,
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
@@ -195,9 +241,15 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
|
||||
if let AttrVarPolicy::DeepCopy = self.attr_var_policy {
|
||||
self.target.push(attr_var_as_cell!(threshold));
|
||||
self.target.push(heap_loc_as_cell!(threshold + 1));
|
||||
|
||||
let list_val = self.target[h + 1];
|
||||
self.target.push(list_val);
|
||||
let old_list_link = self.target[h + 1];
|
||||
self.trail.push((Ref::heap_cell(h + 1), old_list_link));
|
||||
self.target[h + 1] = heap_loc_as_cell!(threshold + 1);
|
||||
|
||||
if old_list_link.get_tag() == HeapCellValueTag::Lis {
|
||||
self.attr_var_list_locs.push((threshold + 1, old_list_link));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -298,8 +350,6 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
self.unwind_trail();
|
||||
}
|
||||
|
||||
fn unwind_trail(&mut self) {
|
||||
|
||||
@@ -0,0 +1,837 @@
|
||||
use crate::atom_table::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
use crate::machine::loader::*;
|
||||
use crate::machine::machine_errors::CompilationError;
|
||||
use crate::machine::preprocessor::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::dashu::Rational;
|
||||
use crate::variable_records::*;
|
||||
|
||||
use dashu::Integer;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)]
|
||||
pub struct BranchNumber {
|
||||
branch_num: Rational,
|
||||
delta: Rational,
|
||||
}
|
||||
|
||||
impl Default for BranchNumber {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
branch_num: Rational::from(1usize << 63),
|
||||
delta: Rational::from(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<BranchNumber> for BranchNumber {
|
||||
#[inline]
|
||||
fn eq(&self, rhs: &BranchNumber) -> bool {
|
||||
self.branch_num == rhs.branch_num
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for BranchNumber {}
|
||||
|
||||
impl Hash for BranchNumber {
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.branch_num.hash(hasher)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<BranchNumber> for BranchNumber {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, rhs: &BranchNumber) -> Option<Ordering> {
|
||||
self.branch_num.partial_cmp(&rhs.branch_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl BranchNumber {
|
||||
fn split(&self) -> BranchNumber {
|
||||
BranchNumber {
|
||||
branch_num: self.branch_num.clone() + &self.delta / Rational::from(2),
|
||||
delta: &self.delta / Rational::from(4),
|
||||
}
|
||||
}
|
||||
|
||||
fn incr_by_delta(&self) -> BranchNumber {
|
||||
BranchNumber {
|
||||
branch_num: self.branch_num.clone() + &self.delta,
|
||||
delta: self.delta.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn halve_delta(&self) -> BranchNumber {
|
||||
BranchNumber {
|
||||
branch_num: self.branch_num.clone(),
|
||||
delta : &self.delta / Rational::from(2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VarInfo {
|
||||
var_ptr: VarPtr,
|
||||
chunk_type: ChunkType,
|
||||
classify_info: ClassifyInfo,
|
||||
lvl: Level,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ChunkInfo {
|
||||
chunk_num: usize,
|
||||
term_loc: GenContext,
|
||||
// pointer to incidence, term occurrence arity.
|
||||
vars: Vec<VarInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BranchArm {
|
||||
pub arm_terms: Vec<QueryTerm>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BranchInfo {
|
||||
branch_num: BranchNumber,
|
||||
chunks: Vec<ChunkInfo>,
|
||||
}
|
||||
|
||||
impl BranchInfo {
|
||||
fn new(branch_num: BranchNumber) -> Self {
|
||||
Self { branch_num, chunks: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
type BranchMapInt = IndexMap<VarPtr, Vec<BranchInfo>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BranchMap(BranchMapInt);
|
||||
|
||||
impl Deref for BranchMap {
|
||||
type Target = BranchMapInt;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &BranchMapInt {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BranchMap {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut BranchMapInt {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
type RootSet = IndexSet<BranchNumber>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ClassifyInfo {
|
||||
arg_c: usize,
|
||||
arity: usize,
|
||||
}
|
||||
|
||||
enum TraversalState {
|
||||
// construct a QueryTerm::Branch with number of disjuncts, reset
|
||||
// the chunk type to that of the chunk preceding the disjunct and the chunk_num.
|
||||
BuildDisjunct(usize),
|
||||
// add the last disjunct to a QueryTerm::Branch, continuing from
|
||||
// where it leaves off.
|
||||
BuildFinalDisjunct(usize),
|
||||
Fail,
|
||||
GetCutPoint{ var_num: usize, prev_b: bool },
|
||||
Cut { var_num: usize, is_global: bool },
|
||||
ResetCallPolicy(CallPolicy),
|
||||
Term(Term),
|
||||
RemoveBranchNum, // pop the current_branch_num and from the root set.
|
||||
AddBranchNum(BranchNumber), // set current_branch_num, add it to the root set
|
||||
RepBranchNum(BranchNumber), // replace current_branch_num and the latest in the root set
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VariableClassifier {
|
||||
call_policy: CallPolicy,
|
||||
current_branch_num: BranchNumber,
|
||||
current_chunk_num: usize,
|
||||
current_chunk_type: ChunkType,
|
||||
branch_map: BranchMap,
|
||||
var_num: usize,
|
||||
root_set: RootSet,
|
||||
global_cut_var_num: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VarData {
|
||||
pub records: VariableRecords,
|
||||
pub global_cut_var_num: Option<usize>,
|
||||
pub allocates: bool,
|
||||
}
|
||||
|
||||
impl VarData {
|
||||
fn emit_initial_get_level(&mut self, build_stack: &mut ChunkedTermVec) {
|
||||
let global_cut_var_num =
|
||||
if let &Some(global_cut_var_num) = &self.global_cut_var_num {
|
||||
match &self.records[global_cut_var_num].allocation {
|
||||
VarAlloc::Perm(..) => Some(global_cut_var_num),
|
||||
VarAlloc::Temp { term_loc, .. } if term_loc.chunk_num() > 0 => {
|
||||
Some(global_cut_var_num)
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(global_cut_var_num) = global_cut_var_num {
|
||||
let term = QueryTerm::GetLevel(global_cut_var_num);
|
||||
self.records[global_cut_var_num].allocation = VarAlloc::Perm(0, PermVarAllocation::Pending);
|
||||
|
||||
match build_stack.front_mut() {
|
||||
Some(ChunkedTerms::Branch(_)) => {
|
||||
build_stack.push_front(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
}
|
||||
Some(ChunkedTerms::Chunk(chunk)) => {
|
||||
chunk.push_front(term);
|
||||
}
|
||||
None => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ClassifyFactResult = (Term, VarData);
|
||||
pub type ClassifyRuleResult = (Term, ChunkedTermVec, VarData);
|
||||
|
||||
fn merge_branch_seq(branches: impl Iterator<Item = BranchInfo>) -> BranchInfo {
|
||||
let mut branch_info = BranchInfo::new(BranchNumber::default());
|
||||
|
||||
for mut branch in branches {
|
||||
branch_info.branch_num = branch.branch_num;
|
||||
branch_info.chunks.extend(branch.chunks.drain(..));
|
||||
}
|
||||
|
||||
branch_info.branch_num.delta = branch_info.branch_num.delta * Integer::from(2);
|
||||
branch_info.branch_num.branch_num -= &branch_info.branch_num.delta;
|
||||
|
||||
branch_info
|
||||
}
|
||||
|
||||
fn flatten_into_disjunct(build_stack: &mut ChunkedTermVec, preceding_len: usize) {
|
||||
let branch_vec = build_stack.drain(preceding_len + 1 ..).collect();
|
||||
|
||||
if let ChunkedTerms::Branch(ref mut disjuncts) = &mut build_stack[preceding_len] {
|
||||
disjuncts.push(branch_vec);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl VariableClassifier {
|
||||
pub fn new(call_policy: CallPolicy) -> Self {
|
||||
Self {
|
||||
call_policy,
|
||||
current_branch_num: BranchNumber::default(),
|
||||
current_chunk_num: 0,
|
||||
current_chunk_type: ChunkType::Head,
|
||||
branch_map: BranchMap(BranchMapInt::new()),
|
||||
root_set: RootSet::new(),
|
||||
var_num: 0,
|
||||
global_cut_var_num: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn classify_fact(mut self, term: Term) -> Result<ClassifyFactResult, CompilationError> {
|
||||
self.classify_head_variables(&term)?;
|
||||
Ok((term, self.branch_map.separate_and_classify_variables(
|
||||
self.var_num,
|
||||
self.global_cut_var_num,
|
||||
self.current_chunk_num,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn classify_rule<'a, LS: LoadState<'a>>(
|
||||
mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
head: Term,
|
||||
body: Term,
|
||||
) -> Result<ClassifyRuleResult, CompilationError> {
|
||||
self.classify_head_variables(&head)?;
|
||||
self.root_set.insert(self.current_branch_num.clone());
|
||||
|
||||
let mut query_terms = self.classify_body_variables(loader, body)?;
|
||||
|
||||
self.merge_branches();
|
||||
|
||||
let mut var_data = self.branch_map.separate_and_classify_variables(
|
||||
self.var_num,
|
||||
self.global_cut_var_num,
|
||||
self.current_chunk_num,
|
||||
);
|
||||
|
||||
var_data.emit_initial_get_level(&mut query_terms);
|
||||
|
||||
Ok((head, query_terms, var_data))
|
||||
}
|
||||
|
||||
fn merge_branches(&mut self) {
|
||||
for branches in self.branch_map.values_mut() {
|
||||
let mut old_branches = std::mem::replace(branches, vec![]);
|
||||
|
||||
while let Some(last_branch_num) = old_branches.last().map(|bi| &bi.branch_num) {
|
||||
let mut old_branches_len = old_branches.len();
|
||||
|
||||
for (rev_idx, bi) in old_branches.iter().rev().enumerate() {
|
||||
if &bi.branch_num > last_branch_num {
|
||||
old_branches_len = old_branches.len() - rev_idx;
|
||||
}
|
||||
}
|
||||
|
||||
let iter = old_branches.drain(old_branches_len - 1 ..);
|
||||
branches.push(merge_branch_seq(iter));
|
||||
}
|
||||
|
||||
branches.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_chunk_at_inlined_boundary(&mut self) -> bool {
|
||||
if self.current_chunk_type.is_last() {
|
||||
self.current_chunk_type = ChunkType::Mid;
|
||||
self.current_chunk_num += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn try_set_chunk_at_call_boundary(&mut self) -> bool {
|
||||
if self.current_chunk_type.is_last() {
|
||||
self.current_chunk_num += 1;
|
||||
true
|
||||
} else {
|
||||
self.current_chunk_type = ChunkType::Last;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_body_term(&mut self, arg_c: usize, arity: usize, term: &Term) {
|
||||
let classify_info = ClassifyInfo { arg_c, arity };
|
||||
|
||||
// second arg is true to iterate the root, which may be a variable
|
||||
for term_ref in breadth_first_iter(term, RootIterationPolicy::Iterated) {
|
||||
if let TermRef::Var(lvl, _, var_ptr) = term_ref {
|
||||
// root terms are shallow here (since we're iterating a
|
||||
// body term) so take the child level.
|
||||
let lvl = lvl.child_level();
|
||||
self.probe_body_var(VarInfo {
|
||||
var_ptr,
|
||||
lvl,
|
||||
classify_info,
|
||||
chunk_type: self.current_chunk_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_body_var(&mut self, var_info: VarInfo) {
|
||||
let term_loc = self.current_chunk_type.to_gen_context(self.current_chunk_num);
|
||||
|
||||
let branch_info_v = self.branch_map.entry(var_info.var_ptr.clone())
|
||||
.or_insert_with(|| vec![]);
|
||||
|
||||
let needs_new_branch = if let Some(last_bi) = branch_info_v.last() {
|
||||
!self.root_set.contains(&last_bi.branch_num)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if needs_new_branch {
|
||||
branch_info_v.push(BranchInfo::new(self.current_branch_num.clone()));
|
||||
}
|
||||
|
||||
let branch_info = branch_info_v.last_mut().unwrap();
|
||||
|
||||
let needs_new_chunk = if let Some(last_ci) = branch_info.chunks.last() {
|
||||
last_ci.chunk_num != self.current_chunk_num
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if needs_new_chunk {
|
||||
branch_info.chunks.push(ChunkInfo {
|
||||
chunk_num: self.current_chunk_num,
|
||||
term_loc,
|
||||
vars: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let chunk_info = branch_info.chunks.last_mut().unwrap();
|
||||
chunk_info.vars.push(var_info);
|
||||
}
|
||||
|
||||
fn probe_in_situ_var(&mut self, var_num: usize) {
|
||||
let classify_info = ClassifyInfo { arg_c: 1, arity: 1 };
|
||||
|
||||
let var_info = VarInfo {
|
||||
var_ptr: VarPtr::from(Var::InSitu(var_num)),
|
||||
classify_info,
|
||||
chunk_type: self.current_chunk_type,
|
||||
lvl: Level::Shallow,
|
||||
};
|
||||
|
||||
self.probe_body_var(var_info);
|
||||
}
|
||||
|
||||
fn classify_head_variables(&mut self, term: &Term) -> Result<(), CompilationError> {
|
||||
match term {
|
||||
Term::Clause(..) | Term::Literal(_, Literal::Atom(_)) => {
|
||||
}
|
||||
_ => return Err(CompilationError::InvalidRuleHead),
|
||||
}
|
||||
|
||||
let mut classify_info = ClassifyInfo { arg_c: 1, arity: term.arity() };
|
||||
|
||||
match term {
|
||||
Term::Clause(_, _, terms) => {
|
||||
for term in terms.into_iter() {
|
||||
for term_ref in breadth_first_iter(term, RootIterationPolicy::Iterated) {
|
||||
if let TermRef::Var(lvl, _, var_ptr) = term_ref {
|
||||
// a body term, so we need the child level here.
|
||||
let lvl = lvl.child_level();
|
||||
|
||||
// the body of the if let here is an inlined
|
||||
// "probe_head_var". note the difference between it
|
||||
// and "probe_body_var".
|
||||
let branch_info_v = self.branch_map.entry(var_ptr.clone())
|
||||
.or_insert_with(|| vec![]);
|
||||
|
||||
let needs_new_branch = branch_info_v.is_empty();
|
||||
|
||||
if needs_new_branch {
|
||||
branch_info_v.push(BranchInfo::new(self.current_branch_num.clone()));
|
||||
}
|
||||
|
||||
let branch_info = branch_info_v.last_mut().unwrap();
|
||||
let needs_new_chunk = branch_info.chunks.is_empty();
|
||||
|
||||
if needs_new_chunk {
|
||||
branch_info.chunks.push(ChunkInfo {
|
||||
chunk_num: self.current_chunk_num,
|
||||
term_loc: GenContext::Head,
|
||||
vars: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let chunk_info = branch_info.chunks.last_mut().unwrap();
|
||||
let var_info = VarInfo {
|
||||
var_ptr,
|
||||
classify_info,
|
||||
chunk_type: self.current_chunk_type,
|
||||
lvl,
|
||||
};
|
||||
|
||||
chunk_info.vars.push(var_info);
|
||||
}
|
||||
}
|
||||
|
||||
classify_info.arg_c += 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn classify_body_variables<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
term: Term,
|
||||
) -> Result<ChunkedTermVec, CompilationError> {
|
||||
let mut state_stack = vec![TraversalState::Term(term)];
|
||||
let mut build_stack = ChunkedTermVec::new();
|
||||
|
||||
self.current_chunk_type = ChunkType::Mid;
|
||||
|
||||
while let Some(traversal_st) = state_stack.pop() {
|
||||
match traversal_st {
|
||||
TraversalState::AddBranchNum(branch_num) => {
|
||||
self.root_set.insert(branch_num.clone());
|
||||
self.current_branch_num = branch_num;
|
||||
}
|
||||
TraversalState::RemoveBranchNum => {
|
||||
self.root_set.pop();
|
||||
}
|
||||
TraversalState::RepBranchNum(branch_num) => {
|
||||
self.root_set.pop();
|
||||
self.root_set.insert(branch_num.clone());
|
||||
self.current_branch_num = branch_num;
|
||||
}
|
||||
TraversalState::ResetCallPolicy(call_policy) => {
|
||||
self.call_policy = call_policy;
|
||||
}
|
||||
TraversalState::BuildDisjunct(preceding_len) => {
|
||||
flatten_into_disjunct(&mut build_stack, preceding_len);
|
||||
|
||||
self.current_chunk_type = ChunkType::Mid;
|
||||
self.current_chunk_num += 1;
|
||||
}
|
||||
TraversalState::BuildFinalDisjunct(preceding_len) => {
|
||||
flatten_into_disjunct(&mut build_stack, preceding_len);
|
||||
|
||||
self.current_chunk_type = ChunkType::Mid;
|
||||
self.current_chunk_num += 1;
|
||||
}
|
||||
TraversalState::GetCutPoint { var_num, prev_b } => {
|
||||
if self.try_set_chunk_at_inlined_boundary() {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
self.probe_in_situ_var(var_num);
|
||||
build_stack.push_chunk_term(QueryTerm::GetCutPoint { var_num, prev_b });
|
||||
}
|
||||
TraversalState::Cut { var_num, is_global } => {
|
||||
if self.try_set_chunk_at_inlined_boundary() {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
self.probe_in_situ_var(var_num);
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
if is_global {
|
||||
QueryTerm::GlobalCut(var_num)
|
||||
} else {
|
||||
QueryTerm::LocalCut(var_num)
|
||||
}
|
||||
);
|
||||
}
|
||||
TraversalState::Fail => {
|
||||
build_stack.push_chunk_term(QueryTerm::Fail);
|
||||
}
|
||||
TraversalState::Term(term) => {
|
||||
// return true iff new chunk should be added.
|
||||
let update_chunk_data = |classifier: &mut Self, predicate_name, arity| {
|
||||
if ClauseType::is_inlined(predicate_name, arity) {
|
||||
classifier.try_set_chunk_at_inlined_boundary()
|
||||
} else {
|
||||
classifier.try_set_chunk_at_call_boundary()
|
||||
}
|
||||
};
|
||||
|
||||
match term {
|
||||
Term::Clause(_, atom!(","), mut terms) if terms.len() == 2 => {
|
||||
let tail = terms.pop().unwrap();
|
||||
let head = terms.pop().unwrap();
|
||||
|
||||
let iter = unfold_by_str(tail, atom!(","))
|
||||
.into_iter()
|
||||
.rev()
|
||||
.chain(std::iter::once(head))
|
||||
.map(TraversalState::Term);
|
||||
|
||||
state_stack.extend(iter);
|
||||
}
|
||||
Term::Clause(_, atom!(";"), mut terms) if terms.len() == 2 => {
|
||||
let tail = terms.pop().unwrap();
|
||||
let head = terms.pop().unwrap();
|
||||
|
||||
let first_branch_num = self.current_branch_num.split();
|
||||
let branches: Vec<_> = std::iter::once(head)
|
||||
.chain(unfold_by_str(tail, atom!(";")).into_iter())
|
||||
.collect();
|
||||
|
||||
let mut branch_numbers = vec![first_branch_num];
|
||||
|
||||
for idx in 1 .. branches.len() {
|
||||
let succ_branch_number = branch_numbers[idx - 1].incr_by_delta();
|
||||
|
||||
branch_numbers.push(if idx + 1 < branches.len() {
|
||||
succ_branch_number.split()
|
||||
} else {
|
||||
succ_branch_number
|
||||
});
|
||||
}
|
||||
|
||||
let build_stack_len = build_stack.len();
|
||||
build_stack.reserve_branch(branches.len());
|
||||
|
||||
state_stack.push(TraversalState::RepBranchNum(
|
||||
self.current_branch_num.halve_delta(),
|
||||
));
|
||||
|
||||
let iter = branches.into_iter().zip(branch_numbers.into_iter());
|
||||
let final_disjunct_loc = state_stack.len();
|
||||
|
||||
for (term, branch_num) in iter.rev() {
|
||||
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
|
||||
state_stack.push(TraversalState::RemoveBranchNum);
|
||||
state_stack.push(TraversalState::Term(term));
|
||||
state_stack.push(TraversalState::AddBranchNum(branch_num));
|
||||
}
|
||||
|
||||
if let TraversalState::BuildDisjunct(build_stack_len) = state_stack[final_disjunct_loc] {
|
||||
state_stack[final_disjunct_loc] = TraversalState::BuildFinalDisjunct(build_stack_len);
|
||||
}
|
||||
|
||||
self.current_chunk_type = ChunkType::Mid;
|
||||
self.current_chunk_num += 1;
|
||||
}
|
||||
Term::Clause(_, atom!("->"), mut terms) if terms.len() == 2 => {
|
||||
let then_term = terms.pop().unwrap();
|
||||
let if_term = terms.pop().unwrap();
|
||||
|
||||
let prev_b = if matches!(state_stack.last(), Some(TraversalState::RemoveBranchNum)) {
|
||||
// check if the second-to-last element is a regular BuildDisjunct, as we don't
|
||||
// want to add GetPrevLevel in case of a TrustMe.
|
||||
matches!(state_stack.iter().rev().nth(1), Some(TraversalState::BuildDisjunct(..)))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
state_stack.push(TraversalState::Term(then_term));
|
||||
state_stack.push(TraversalState::Cut { var_num: self.var_num, is_global: false });
|
||||
state_stack.push(TraversalState::Term(if_term));
|
||||
state_stack.push(TraversalState::GetCutPoint { var_num: self.var_num, prev_b });
|
||||
|
||||
self.var_num += 1;
|
||||
}
|
||||
Term::Clause(_, atom!("\\+"), mut terms) if terms.len() == 1 => {
|
||||
let not_term = terms.pop().unwrap();
|
||||
let build_stack_len = build_stack.len();
|
||||
|
||||
build_stack.reserve_branch(2);
|
||||
|
||||
state_stack.push(TraversalState::BuildFinalDisjunct(build_stack_len));
|
||||
state_stack.push(TraversalState::Term(Term::Clause(Cell::default(), atom!("$succeed"), vec![])));
|
||||
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
|
||||
state_stack.push(TraversalState::Fail);
|
||||
state_stack.push(TraversalState::Cut { var_num: self.var_num, is_global: false });
|
||||
state_stack.push(TraversalState::Term(not_term));
|
||||
state_stack.push(TraversalState::GetCutPoint { var_num: self.var_num, prev_b: true });
|
||||
|
||||
self.current_chunk_type = ChunkType::Mid;
|
||||
self.current_chunk_num += 1;
|
||||
|
||||
self.var_num += 1;
|
||||
}
|
||||
Term::Clause(_, atom!(":"), mut terms) if terms.len() == 2 => {
|
||||
let predicate_name = terms.pop().unwrap();
|
||||
let module_name = terms.pop().unwrap();
|
||||
|
||||
match (module_name, predicate_name) {
|
||||
(
|
||||
Term::Literal(_, Literal::Atom(module_name)),
|
||||
Term::Literal(_, Literal::Atom(predicate_name)),
|
||||
) => {
|
||||
if update_chunk_data(self, predicate_name, 0) {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
qualified_clause_to_query_term(
|
||||
loader,
|
||||
module_name,
|
||||
predicate_name,
|
||||
vec![],
|
||||
self.call_policy,
|
||||
),
|
||||
);
|
||||
}
|
||||
(
|
||||
Term::Literal(_, Literal::Atom(module_name)),
|
||||
Term::Clause(_, name, terms),
|
||||
) => {
|
||||
if update_chunk_data(self, name, terms.len()) {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
for (arg_c, term) in terms.iter().enumerate() {
|
||||
self.probe_body_term(arg_c + 1, terms.len(), term);
|
||||
}
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
qualified_clause_to_query_term(
|
||||
loader,
|
||||
module_name,
|
||||
name,
|
||||
terms,
|
||||
self.call_policy,
|
||||
),
|
||||
);
|
||||
}
|
||||
(module_name, predicate_name) => {
|
||||
if update_chunk_data(self, atom!("call"), 2) {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
self.probe_body_term(1, 0, &module_name);
|
||||
self.probe_body_term(2, 0, &predicate_name);
|
||||
|
||||
terms.push(module_name);
|
||||
terms.push(predicate_name);
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
clause_to_query_term(
|
||||
loader,
|
||||
atom!("call"),
|
||||
vec![Term::Clause(Cell::default(), atom!(":"), terms)],
|
||||
self.call_policy,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Term::Clause(_, atom!("$call_with_inference_counting"), mut terms) if terms.len() == 1 => {
|
||||
state_stack.push(TraversalState::ResetCallPolicy(self.call_policy));
|
||||
state_stack.push(TraversalState::Term(terms.pop().unwrap()));
|
||||
|
||||
self.call_policy = CallPolicy::Counted;
|
||||
}
|
||||
Term::Clause(_, name, terms) => {
|
||||
if update_chunk_data(self, name, terms.len()) {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
for (arg_c, term) in terms.iter().enumerate() {
|
||||
self.probe_body_term(arg_c + 1, terms.len(), term);
|
||||
}
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
clause_to_query_term(
|
||||
loader,
|
||||
name,
|
||||
terms,
|
||||
self.call_policy,
|
||||
),
|
||||
);
|
||||
}
|
||||
var @ Term::Var(..) => {
|
||||
if update_chunk_data(self, atom!("call"), 1) {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
self.probe_body_term(1, 1, &var);
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
clause_to_query_term(
|
||||
loader,
|
||||
atom!("call"),
|
||||
vec![var],
|
||||
self.call_policy,
|
||||
),
|
||||
);
|
||||
}
|
||||
Term::Literal(_, Literal::Atom(atom!("!")) | Literal::Char('!')) => {
|
||||
if self.global_cut_var_num.is_none() {
|
||||
self.global_cut_var_num = Some(self.var_num);
|
||||
self.var_num += 1;
|
||||
}
|
||||
|
||||
self.probe_in_situ_var(self.global_cut_var_num.unwrap());
|
||||
|
||||
state_stack.push(TraversalState::Cut {
|
||||
var_num: self.global_cut_var_num.unwrap(),
|
||||
is_global: true,
|
||||
});
|
||||
}
|
||||
Term::Literal(_, Literal::Atom(name)) => {
|
||||
if update_chunk_data(self, name, 0) {
|
||||
build_stack.add_chunk();
|
||||
}
|
||||
|
||||
build_stack.push_chunk_term(
|
||||
clause_to_query_term(
|
||||
loader,
|
||||
name,
|
||||
vec![],
|
||||
self.call_policy,
|
||||
),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Err(CompilationError::InadmissibleQueryTerm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(build_stack)
|
||||
}
|
||||
}
|
||||
|
||||
impl BranchMap {
|
||||
pub fn separate_and_classify_variables(
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
global_cut_var_num: Option<usize>,
|
||||
current_chunk_num: usize,
|
||||
) -> VarData {
|
||||
let mut var_data = VarData {
|
||||
records: VariableRecords::new(var_num),
|
||||
global_cut_var_num,
|
||||
allocates: current_chunk_num > 0,
|
||||
};
|
||||
|
||||
for (var, branches) in self.iter_mut() {
|
||||
let (mut var_num, var_num_incr) =
|
||||
if let Var::InSitu(var_num) = *var.borrow() {
|
||||
(var_num, false)
|
||||
} else {
|
||||
(var_data.records.len(), true)
|
||||
};
|
||||
|
||||
for branch in branches.iter_mut() {
|
||||
if var_num_incr {
|
||||
var_num = var_data.records.len();
|
||||
var_data.records.push(VariableRecord::default());
|
||||
}
|
||||
|
||||
if branch.chunks.len() <= 1 { // true iff var is a temporary variable.
|
||||
debug_assert_eq!(branch.chunks.len(), 1);
|
||||
|
||||
let chunk = &mut branch.chunks[0];
|
||||
let mut temp_var_data = TempVarData::new();
|
||||
|
||||
for var_info in chunk.vars.iter_mut() {
|
||||
if var_info.lvl == Level::Shallow {
|
||||
let term_loc = var_info.chunk_type.to_gen_context(chunk.chunk_num);
|
||||
temp_var_data.use_set.insert((term_loc, var_info.classify_info.arg_c));
|
||||
}
|
||||
}
|
||||
|
||||
var_data.records[var_num].allocation = VarAlloc::Temp {
|
||||
term_loc: chunk.term_loc,
|
||||
temp_reg: 0,
|
||||
temp_var_data,
|
||||
safety: VarSafetyStatus::Needed,
|
||||
to_perm_var_num: None,
|
||||
};
|
||||
} // else VarAlloc is already a Perm variant, as it's the default.
|
||||
|
||||
for chunk in branch.chunks.iter_mut() {
|
||||
var_data.records[var_num].num_occurrences += chunk.vars.len();
|
||||
|
||||
for var_info in chunk.vars.iter_mut() {
|
||||
var_info.var_ptr.set(Var::Generated(var_num));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var_data.records.populate_restricting_sets();
|
||||
var_data
|
||||
}
|
||||
}
|
||||
+1061
-824
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -3,7 +3,7 @@ use crate::machine::heap::*;
|
||||
use crate::types::*;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::heap_iter::FocusedHeapIter;
|
||||
use crate::heap_iter::{IterStackLoc, FocusedHeapIter, HeapOrStackTag};
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
@@ -75,8 +75,8 @@ pub(crate) struct StacklessPreOrderHeapIter<'a, UMP: UnmarkPolicy> {
|
||||
#[cfg(test)]
|
||||
impl<'a> FocusedHeapIter for StacklessPreOrderHeapIter<'a, IteratorUMP> {
|
||||
#[inline]
|
||||
fn focus(&self) -> usize {
|
||||
self.current
|
||||
fn focus(&self) -> IterStackLoc {
|
||||
IterStackLoc::iterable_loc(self.current, HeapOrStackTag::Heap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ use crate::machine::partial_string::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::types::*;
|
||||
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ pub(super) fn import_module_exports<'a, LS: LoadState<'a>>(
|
||||
meta_predicates.insert(key, meta_specs.clone());
|
||||
}
|
||||
|
||||
if let Some(src_code_index) = imported_module.code_dir.get(&key) {
|
||||
if let Some(src_code_index) = imported_module.code_dir.get(&key).cloned() {
|
||||
let arena = &mut LS::machine_st(payload).arena;
|
||||
|
||||
let target_code_index = code_dir
|
||||
@@ -148,6 +148,10 @@ pub(super) fn import_module_exports<'a, LS: LoadState<'a>>(
|
||||
target_code_index,
|
||||
src_code_index.get(),
|
||||
);
|
||||
|
||||
if src_code_index.is_dynamic_undefined() {
|
||||
code_dir.insert(key, src_code_index);
|
||||
}
|
||||
} else {
|
||||
return Err(SessionError::ModuleDoesNotContainExport(
|
||||
imported_module.module_decl.name,
|
||||
@@ -441,13 +445,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
term: Term,
|
||||
preprocessor: &mut Preprocessor,
|
||||
) -> Result<PredicateClause, SessionError> {
|
||||
let tl = preprocessor.try_term_to_tl(self, term, CutContext::BlocksCuts)?;
|
||||
let tl = preprocessor.try_term_to_tl(self, term)?;
|
||||
|
||||
Ok(match tl {
|
||||
TopLevel::Fact(fact) => PredicateClause::Fact(fact),
|
||||
TopLevel::Rule(rule) => PredicateClause::Rule(rule),
|
||||
TopLevel::Query(_) => return Err(SessionError::QueryCannotBeDefinedAsFact),
|
||||
_ => unreachable!(),
|
||||
TopLevel::Fact(fact, var_data) => PredicateClause::Fact(fact, var_data),
|
||||
TopLevel::Rule(rule, var_data) => PredicateClause::Rule(rule, var_data),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+147
-137
@@ -21,7 +21,6 @@ use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
/*
|
||||
* The loader compiles Prolog terms read from a TermStream instance,
|
||||
@@ -329,6 +328,10 @@ impl<'a> LoadState<'a> for LiveLoadAndMachineState<'a> {
|
||||
loader: &Loader<'a, Self>,
|
||||
key: PredicateKey,
|
||||
) -> Result<(), SessionError> {
|
||||
if ClauseType::is_inbuilt(key.0, key.1) {
|
||||
return Err(SessionError::CannotOverwriteBuiltIn(key));
|
||||
}
|
||||
|
||||
if let Some(builtins) = loader.wam_prelude.indices.modules.get(&atom!("builtins")) {
|
||||
if builtins.module_decl.exports.contains(&ModuleExport::PredicateKey(key)) {
|
||||
return Err(SessionError::CannotOverwriteBuiltIn(key));
|
||||
@@ -465,6 +468,13 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_term_from_heap(&mut self, r: RegType) -> Result<Term, SessionError> {
|
||||
let machine_st = LS::machine_st(&mut self.payload);
|
||||
let cell = machine_st[r];
|
||||
|
||||
machine_st.read_term_from_heap(cell)
|
||||
}
|
||||
|
||||
pub(crate) fn load(mut self) -> Result<LS::Evacuable, SessionError> {
|
||||
while let Some(decl) = self.dequeue_terms()? {
|
||||
self.load_decl(decl)?;
|
||||
@@ -531,106 +541,6 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn read_term_from_heap(&mut self, heap_term_loc: RegType) -> Result<Term, SessionError> {
|
||||
let machine_st = LS::machine_st(&mut self.payload);
|
||||
let term_addr = machine_st[heap_term_loc];
|
||||
|
||||
let mut term_stack = vec![];
|
||||
let mut iter = stackful_post_order_iter(&mut machine_st.heap, term_addr);
|
||||
|
||||
while let Some(addr) = iter.next() {
|
||||
let addr = unmark_cell_bits!(addr);
|
||||
|
||||
read_heap_cell!(addr,
|
||||
(HeapCellValueTag::Lis) => {
|
||||
use crate::parser::parser::as_partial_string;
|
||||
|
||||
let tail = term_stack.pop().unwrap();
|
||||
let head = term_stack.pop().unwrap();
|
||||
|
||||
match as_partial_string(head, tail) {
|
||||
Ok((string, Some(tail))) => {
|
||||
term_stack.push(Term::PartialString(Cell::default(), string, tail));
|
||||
}
|
||||
Ok((string, None)) => {
|
||||
let atom = machine_st.atom_tbl.build_with(&string);
|
||||
term_stack.push(Term::CompleteString(Cell::default(), atom));
|
||||
}
|
||||
Err(cons_term) => term_stack.push(cons_term),
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar, h) => {
|
||||
let offset_string = format!("_{}", h);
|
||||
term_stack.push(Term::Var(Cell::default(), Rc::new(offset_string)));
|
||||
}
|
||||
(HeapCellValueTag::Cons | HeapCellValueTag::CStr | HeapCellValueTag::Fixnum |
|
||||
HeapCellValueTag::Char | HeapCellValueTag::F64) => {
|
||||
term_stack.push(Term::Literal(Cell::default(), Literal::try_from(addr).unwrap()));
|
||||
}
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
let h = iter.focus();
|
||||
let mut arity = arity;
|
||||
|
||||
if iter.heap.len() > h + arity + 1 {
|
||||
let value = iter.heap[h + arity + 1];
|
||||
|
||||
if let Some(idx) = get_structure_index(value) {
|
||||
// in the second condition, arity == 0,
|
||||
// meaning idx cannot pertain to this atom
|
||||
// if it is the direct subterm of a larger
|
||||
// structure.
|
||||
if arity > 0 || !iter.direct_subterm_of_str(h) {
|
||||
term_stack.push(
|
||||
Term::Literal(Cell::default(), Literal::CodeIndex(idx))
|
||||
);
|
||||
|
||||
arity += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if arity == 0 {
|
||||
term_stack.push(Term::Literal(Cell::default(), Literal::Atom(name)));
|
||||
} else {
|
||||
let subterms = term_stack
|
||||
.drain(term_stack.len() - arity ..)
|
||||
.collect();
|
||||
|
||||
term_stack.push(Term::Clause(Cell::default(), name, subterms));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStr, atom) => {
|
||||
let tail = term_stack.pop().unwrap();
|
||||
|
||||
if let Term::Literal(_, Literal::Atom(atom!("[]"))) = &tail {
|
||||
term_stack.push(Term::CompleteString(Cell::default(), atom));
|
||||
} else {
|
||||
term_stack.push(Term::PartialString(
|
||||
Cell::default(),
|
||||
atom.as_str().to_owned(),
|
||||
Box::new(tail),
|
||||
));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStrLoc, h) => {
|
||||
let atom = cell_as_atom_cell!(iter.heap[h]).get_name();
|
||||
let tail = term_stack.pop().unwrap();
|
||||
|
||||
term_stack.push(Term::PartialString(
|
||||
Cell::default(),
|
||||
atom.as_str().to_owned(),
|
||||
Box::new(tail),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
debug_assert!(term_stack.len() == 1);
|
||||
Ok(term_stack.pop().unwrap())
|
||||
}
|
||||
|
||||
fn reset_machine(&mut self) {
|
||||
while let Some(record) = self.payload.retraction_info.records.pop() {
|
||||
match record {
|
||||
@@ -1143,7 +1053,10 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
&mut self,
|
||||
r: RegType,
|
||||
) -> Result<IndexSet<ModuleExport>, SessionError> {
|
||||
let export_list = self.read_term_from_heap(r)?;
|
||||
let machine_st = LS::machine_st(&mut self.payload);
|
||||
let cell = machine_st[r];
|
||||
|
||||
let export_list = machine_st.read_term_from_heap(cell)?;
|
||||
let atom_tbl = &mut LS::machine_st(&mut self.payload).atom_tbl;
|
||||
let export_list = setup_module_export_list(export_list, atom_tbl)?;
|
||||
|
||||
@@ -1493,6 +1406,104 @@ impl<'a> MachinePreludeView<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineState {
|
||||
pub(super) fn read_term_from_heap(&mut self, term_addr: HeapCellValue) -> Result<Term, SessionError> {
|
||||
let mut term_stack = vec![];
|
||||
let mut iter = stackful_post_order_iter(&mut self.heap, &mut self.stack, term_addr);
|
||||
|
||||
while let Some(addr) = iter.next() {
|
||||
let addr = unmark_cell_bits!(addr);
|
||||
|
||||
read_heap_cell!(addr,
|
||||
(HeapCellValueTag::Lis) => {
|
||||
use crate::parser::parser::as_partial_string;
|
||||
|
||||
let tail = term_stack.pop().unwrap();
|
||||
let head = term_stack.pop().unwrap();
|
||||
|
||||
match as_partial_string(head, tail) {
|
||||
Ok((string, Some(tail))) => {
|
||||
term_stack.push(Term::PartialString(Cell::default(), string, tail));
|
||||
}
|
||||
Ok((string, None)) => {
|
||||
let atom = self.atom_tbl.build_with(&string);
|
||||
term_stack.push(Term::CompleteString(Cell::default(), atom));
|
||||
}
|
||||
Err(cons_term) => term_stack.push(cons_term),
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar, h) => {
|
||||
term_stack.push(Term::Var(Cell::default(), VarPtr::from(format!("_{}", h))));
|
||||
}
|
||||
(HeapCellValueTag::Cons | HeapCellValueTag::CStr | HeapCellValueTag::Fixnum |
|
||||
HeapCellValueTag::Char | HeapCellValueTag::F64) => {
|
||||
term_stack.push(Term::Literal(Cell::default(), Literal::try_from(addr).unwrap()));
|
||||
}
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
let h = iter.focus().value() as usize;
|
||||
let mut arity = arity;
|
||||
|
||||
if iter.heap.len() > h + arity + 1 {
|
||||
let value = iter.heap[h + arity + 1];
|
||||
|
||||
if let Some(idx) = get_structure_index(value) {
|
||||
// in the second condition, arity == 0,
|
||||
// meaning idx cannot pertain to this atom
|
||||
// if it is the direct subterm of a larger
|
||||
// structure.
|
||||
if arity > 0 || !iter.direct_subterm_of_str(h) {
|
||||
term_stack.push(
|
||||
Term::Literal(Cell::default(), Literal::CodeIndex(idx))
|
||||
);
|
||||
|
||||
arity += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if arity == 0 {
|
||||
term_stack.push(Term::Literal(Cell::default(), Literal::Atom(name)));
|
||||
} else {
|
||||
let subterms = term_stack
|
||||
.drain(term_stack.len() - arity ..)
|
||||
.collect();
|
||||
|
||||
term_stack.push(Term::Clause(Cell::default(), name, subterms));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStr, atom) => {
|
||||
let tail = term_stack.pop().unwrap();
|
||||
|
||||
if let Term::Literal(_, Literal::Atom(atom!("[]"))) = &tail {
|
||||
term_stack.push(Term::CompleteString(Cell::default(), atom));
|
||||
} else {
|
||||
term_stack.push(Term::PartialString(
|
||||
Cell::default(),
|
||||
atom.as_str().to_owned(),
|
||||
Box::new(tail),
|
||||
));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStrLoc, h) => {
|
||||
let atom = cell_as_atom_cell!(iter.heap[h]).get_name();
|
||||
let tail = term_stack.pop().unwrap();
|
||||
|
||||
term_stack.push(Term::PartialString(
|
||||
Cell::default(),
|
||||
atom.as_str().to_owned(),
|
||||
Box::new(tail),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
debug_assert!(term_stack.len() == 1);
|
||||
Ok(term_stack.pop().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub(crate) fn use_module(&mut self) -> CallResult {
|
||||
let subevacuable_addr = self
|
||||
@@ -1620,25 +1631,18 @@ impl Machine {
|
||||
usize,
|
||||
) -> Result<(), SessionError>,
|
||||
) -> CallResult {
|
||||
let module_name = cell_as_atom!(
|
||||
self.machine_st.store(self.machine_st.deref(self.machine_st.registers[1]))
|
||||
);
|
||||
let module_name = cell_as_atom!(self.deref_register(1));
|
||||
|
||||
let compilation_target = match module_name {
|
||||
atom!("user") => CompilationTarget::User,
|
||||
_ => CompilationTarget::Module(module_name),
|
||||
};
|
||||
|
||||
let predicate_name = cell_as_atom!(
|
||||
self.machine_st.store(self.machine_st.deref(self.machine_st.registers[2]))
|
||||
);
|
||||
|
||||
let arity = self
|
||||
.machine_st
|
||||
.store(self.machine_st.deref(self.machine_st.registers[3]));
|
||||
let predicate_name = cell_as_atom!(self.deref_register(2));
|
||||
|
||||
let arity = self.deref_register(3);
|
||||
let arity = match Number::try_from(arity) {
|
||||
Ok(Number::Integer(n)) if &*n >= &0 && &*n <= &MAX_ARITY => Ok(n.to_usize().unwrap()),
|
||||
Ok(Number::Integer(n)) if &*n >= &Integer::from(0) && &*n <= &Integer::from(MAX_ARITY) => Ok(n.to_usize().unwrap()),
|
||||
Ok(Number::Fixnum(n)) if n.get_num() >= 0 && n.get_num() <= MAX_ARITY as i64 => {
|
||||
Ok(usize::try_from(n.get_num()).unwrap())
|
||||
}
|
||||
@@ -1692,6 +1696,21 @@ impl Machine {
|
||||
let add_clause = || {
|
||||
let term = loader.read_term_from_heap(temp_v!(2))?;
|
||||
|
||||
let indexing_arg = match term.name() {
|
||||
Some(atom!(":-")) => term.first_arg().and_then(Term::first_arg),
|
||||
Some(_) => term.first_arg(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
if let Some(indexing_term) = indexing_arg {
|
||||
if let Some(indexing_name) = indexing_term.name() {
|
||||
loader.wam_prelude
|
||||
.indices
|
||||
.goal_expansion_indices
|
||||
.insert((indexing_name, indexing_term.arity()));
|
||||
}
|
||||
}
|
||||
|
||||
loader.incremental_compile_clause(
|
||||
(atom!("goal_expansion"), 2),
|
||||
term,
|
||||
@@ -1962,11 +1981,8 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compile_assert(&mut self, append_or_prepend: AppendOrPrepend) -> CallResult
|
||||
{
|
||||
let module_name = cell_as_atom!(
|
||||
self.machine_st.store(self.machine_st.deref(self.machine_st.registers[1]))
|
||||
);
|
||||
pub(crate) fn compile_assert(&mut self, append_or_prepend: AppendOrPrepend) -> CallResult {
|
||||
let module_name = cell_as_atom!(self.deref_register(1));
|
||||
|
||||
let compilation_target = match module_name {
|
||||
atom!("user") => CompilationTarget::User,
|
||||
@@ -1980,13 +1996,20 @@ impl Machine {
|
||||
}
|
||||
};
|
||||
|
||||
let head = self.deref_register(2);
|
||||
|
||||
if head.is_var() {
|
||||
let err = self.machine_st.instantiation_error();
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
|
||||
let mut compile_assert = || {
|
||||
let mut loader: Loader<'_, LiveLoadAndMachineState<'_>> =
|
||||
Loader::new(self, LiveTermStream::new(ListingSource::User));
|
||||
|
||||
loader.payload.compilation_target = compilation_target;
|
||||
|
||||
let head = loader.read_term_from_heap(temp_v!(2))?;
|
||||
let head = LiveLoadAndMachineState::machine_st(&mut loader.payload).read_term_from_heap(head)?;
|
||||
|
||||
let name = if let Some(name) = head.name() {
|
||||
name
|
||||
@@ -1995,6 +2018,7 @@ impl Machine {
|
||||
};
|
||||
|
||||
let arity = head.arity();
|
||||
let is_builtin = loader.wam_prelude.indices.builtin_property((name, arity));
|
||||
|
||||
let is_dynamic_predicate = loader
|
||||
.wam_prelude
|
||||
@@ -2005,7 +2029,7 @@ impl Machine {
|
||||
);
|
||||
|
||||
let no_such_predicate =
|
||||
if !is_dynamic_predicate && !ClauseType::is_inbuilt(name, arity) {
|
||||
if !is_dynamic_predicate && !is_builtin {
|
||||
let idx_tag = loader
|
||||
.wam_prelude
|
||||
.indices
|
||||
@@ -2017,8 +2041,9 @@ impl Machine {
|
||||
.map(|code_idx| code_idx.get_tag())
|
||||
.unwrap_or(IndexPtrTag::DynamicUndefined);
|
||||
|
||||
idx_tag == IndexPtrTag::DynamicUndefined ||
|
||||
idx_tag == IndexPtrTag::Undefined
|
||||
idx_tag == IndexPtrTag::DynamicUndefined || idx_tag == IndexPtrTag::Undefined
|
||||
} else if is_builtin {
|
||||
return Err(SessionError::CannotOverwriteBuiltIn((name, arity)));
|
||||
} else {
|
||||
is_dynamic_predicate
|
||||
};
|
||||
@@ -2445,21 +2470,6 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_property(&mut self) {
|
||||
let (name, arity) = self
|
||||
.machine_st
|
||||
.read_predicate_key(self.machine_st.registers[1], self.machine_st.registers[2]);
|
||||
|
||||
if !ClauseType::is_inbuilt(name, arity) { // ClauseType::from(key.0, key.1, &mut self.machine_st.arena) {
|
||||
if let Some(module) = self.indices.modules.get(&(atom!("builtins"))) {
|
||||
self.machine_st.fail = !module.code_dir.contains_key(&(name, arity));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.machine_st.fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Loader<'a, LiveLoadAndMachineState<'a>> {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::parser::ast::*;
|
||||
|
||||
use crate::ffi::FFIError;
|
||||
use crate::forms::*;
|
||||
use crate::machine::heap::*;
|
||||
use crate::machine::loader::CompilationTarget;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::machine::streams::*;
|
||||
use crate::machine::system_calls::BrentAlgState;
|
||||
use crate::types::*;
|
||||
|
||||
@@ -157,9 +160,29 @@ impl PermissionError for HeapCellValue {
|
||||
index_atom: Atom,
|
||||
perm: Permission,
|
||||
) -> MachineError {
|
||||
let cell = read_heap_cell!(self,
|
||||
(HeapCellValueTag::Cons, ptr) => {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::Stream, stream) => {
|
||||
if let Some(alias) = stream.options().get_alias() {
|
||||
atom_as_cell!(alias)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self
|
||||
}
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
self
|
||||
}
|
||||
);
|
||||
|
||||
let stub = functor!(
|
||||
atom!("permission_error"),
|
||||
[atom(perm.as_atom()), atom(index_atom), cell(self)]
|
||||
[atom(perm.as_atom()), atom(index_atom), cell(cell)]
|
||||
);
|
||||
|
||||
MachineError {
|
||||
@@ -419,7 +442,7 @@ impl MachineState {
|
||||
// SessionError::CannotOverwriteImport(pred_atom) => {
|
||||
self.permission_error(
|
||||
Permission::Modify,
|
||||
atom!("private_procedure"),
|
||||
atom!("static_procedure"),
|
||||
functor_stub(key.0, key.1).into_iter().collect::<MachineStub>(),
|
||||
)
|
||||
}
|
||||
@@ -515,6 +538,24 @@ impl MachineState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ffi_error(&mut self, err: FFIError) -> MachineError {
|
||||
let error_atom = match err {
|
||||
FFIError::ValueCast => atom!("value_cast"),
|
||||
FFIError::ValueDontFit => atom!("value_dont_fit"),
|
||||
FFIError::InvalidFFIType => atom!("invalid_ffi_type"),
|
||||
FFIError::InvalidStructName => atom!("invalid_struct_name"),
|
||||
FFIError::FunctionNotFound => atom!("function_not_found"),
|
||||
FFIError::StructNotFound => atom!("struct_not_found"),
|
||||
};
|
||||
let stub = functor!(atom!("ffi_error"),[atom(error_atom)]);
|
||||
|
||||
MachineError {
|
||||
stub,
|
||||
location: None,
|
||||
from: ErrorProvenance::Constructed,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn error_form(&mut self, err: MachineError, src: FunctorStub) -> MachineStub {
|
||||
let h = self.heap.len();
|
||||
let location = err.location;
|
||||
@@ -661,7 +702,6 @@ impl CompilationError {
|
||||
functor!(atom!("no_such_module"), [atom(module_name)])
|
||||
}
|
||||
&CompilationError::InvalidRuleHead => {
|
||||
|
||||
functor!(atom!("invalid_head_of_rule")) // TODO: type_error(callable, _).
|
||||
}
|
||||
&CompilationError::InvalidUseModuleDecl => {
|
||||
@@ -780,7 +820,7 @@ pub enum CycleSearchResult {
|
||||
NotList(usize, HeapCellValue), // the list length until the second argument in the heap
|
||||
PartialList(usize, Ref), // the list length (up to max), and an offset into the heap.
|
||||
ProperList(usize), // the list length.
|
||||
PStrLocation(usize, usize), // list length (up to max), the heap address of the PStrOffset
|
||||
PStrLocation(usize, usize, usize), // list length (up to max), the heap address of the PStr, the offset
|
||||
UntouchedList(usize, usize), // list length (up to max), the address of an uniterated Addr::Lis(address).
|
||||
UntouchedCStr(Atom, usize),
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@ use crate::parser::ast::*;
|
||||
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::fixtures::*;
|
||||
use crate::forms::*;
|
||||
use crate::machine::ClauseType;
|
||||
use crate::machine::loader::*;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::machine::streams::Stream;
|
||||
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexMap;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use modular_bitfield::{BitfieldSpecifier, bitfield};
|
||||
use modular_bitfield::specifiers::*;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeSet;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::types::*;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -228,8 +227,32 @@ impl CodeIndex {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type HeapVarDict = IndexMap<Rc<String>, HeapCellValue, FxBuildHasher>;
|
||||
pub(crate) type AllocVarDict = IndexMap<Rc<String>, VarData, FxBuildHasher>;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum VarKey {
|
||||
AnonVar(usize),
|
||||
VarPtr(VarPtr),
|
||||
}
|
||||
|
||||
impl VarKey {
|
||||
#[inline]
|
||||
pub(crate) fn to_string(&self) -> String {
|
||||
match self {
|
||||
VarKey::AnonVar(h) => format!("_{}", h),
|
||||
VarKey::VarPtr(var) => var.borrow().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_anon(&self) -> bool {
|
||||
if let VarKey::AnonVar(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type HeapVarDict = IndexMap<VarKey, HeapCellValue, FxBuildHasher>;
|
||||
|
||||
pub(crate) type GlobalVarDir = IndexMap<Atom, (Ball, Option<HeapCellValue>), FxBuildHasher>;
|
||||
|
||||
@@ -245,12 +268,15 @@ pub(crate) type LocalExtensiblePredicates =
|
||||
|
||||
pub(crate) type CodeDir = IndexMap<PredicateKey, CodeIndex, FxBuildHasher>;
|
||||
|
||||
pub(crate) type GoalExpansionIndices = IndexSet<PredicateKey, FxBuildHasher>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IndexStore {
|
||||
pub(super) code_dir: CodeDir,
|
||||
pub(super) extensible_predicates: ExtensiblePredicates,
|
||||
pub(super) local_extensible_predicates: LocalExtensiblePredicates,
|
||||
pub(super) global_variables: GlobalVarDir,
|
||||
pub(super) goal_expansion_indices: GoalExpansionIndices,
|
||||
pub(super) meta_predicates: MetaPredicateDir,
|
||||
pub(super) modules: ModuleDir,
|
||||
pub(super) op_dir: OpDir,
|
||||
@@ -259,6 +285,23 @@ pub struct IndexStore {
|
||||
}
|
||||
|
||||
impl IndexStore {
|
||||
pub(crate) fn builtin_property(&self, key: PredicateKey) -> bool {
|
||||
let (name, arity) = key;
|
||||
|
||||
if !ClauseType::is_inbuilt(name, arity) {
|
||||
self.modules.get(&(atom!("builtins")))
|
||||
.map(|module| module.code_dir.contains_key(&(name, arity)))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn goal_expansion_defined(&self, key: PredicateKey) -> bool {
|
||||
self.goal_expansion_indices.contains(&key)
|
||||
}
|
||||
|
||||
pub(crate) fn get_predicate_skeleton_mut(
|
||||
&mut self,
|
||||
compilation_target: &CompilationTarget,
|
||||
@@ -371,22 +414,11 @@ impl IndexStore {
|
||||
module: Atom,
|
||||
) -> Option<CodeIndex> {
|
||||
if module == atom!("user") {
|
||||
/*match ClauseType::from(name, arity) {
|
||||
ClauseType::Named(arity, name, _) => */
|
||||
self.code_dir.get(&(name, arity)).cloned()
|
||||
/* _ => None,
|
||||
}*/
|
||||
} else {
|
||||
self.modules
|
||||
.get(&module)
|
||||
.and_then(|module|/* |module| match ClauseType::from(name, arity) {
|
||||
ClauseType::Named(arity, name, _) => { */
|
||||
module.code_dir.get(&(name, arity)).cloned()
|
||||
/*
|
||||
}
|
||||
_ => None,
|
||||
} */
|
||||
)
|
||||
.and_then(|module| module.code_dir.get(&(name, arity)).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+229
-194
@@ -12,16 +12,16 @@ use crate::machine::machine_indices::*;
|
||||
use crate::machine::stack::*;
|
||||
use crate::machine::streams::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::read::TermWriteResult;
|
||||
use crate::types::*;
|
||||
|
||||
use crate::parser::rug::Integer;
|
||||
use crate::parser::dashu::Integer;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) type Registers = [HeapCellValue; MAX_ARITY + 1];
|
||||
|
||||
@@ -50,6 +50,12 @@ pub enum FirstOrNext {
|
||||
Next,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OnEOF {
|
||||
Return,
|
||||
Continue,
|
||||
}
|
||||
|
||||
pub struct MachineState {
|
||||
pub atom_tbl: AtomTable,
|
||||
pub arena: Arena,
|
||||
@@ -74,11 +80,12 @@ pub struct MachineState {
|
||||
pub(super) tr: usize,
|
||||
pub(super) hb: usize,
|
||||
pub(super) block: usize, // an offset into the OR stack.
|
||||
pub(super) scc_block: usize, // an offset into the OR stack for setup_call_cleanup/3.
|
||||
pub(super) ball: Ball,
|
||||
pub(super) ball_stack: Vec<Ball>, // save current ball before jumping via, e.g., verify_attr interrupt.
|
||||
pub(super) lifted_heap: Heap,
|
||||
pub(super) interms: Vec<Number>, // intermediate numbers.
|
||||
// locations of cleaners, cut points, the previous block. for setup_call_cleanup.
|
||||
// locations of cleaners, cut points, the previous scc_block. for setup_call_cleanup/3.
|
||||
pub(super) cont_pts: Vec<(HeapCellValue, usize, usize)>,
|
||||
pub(super) cwil: CWIL,
|
||||
pub(crate) flags: MachineFlags,
|
||||
@@ -113,6 +120,7 @@ impl fmt::Debug for MachineState {
|
||||
.field("tr", &self.tr)
|
||||
.field("hb", &self.hb)
|
||||
.field("block", &self.block)
|
||||
.field("scc_block", &self.scc_block)
|
||||
.field("ball", &self.ball)
|
||||
.field("ball_stack", &self.ball_stack)
|
||||
.field("lifted_heap", &self.lifted_heap)
|
||||
@@ -192,6 +200,27 @@ pub fn pstr_loc_and_offset(heap: &[HeapCellValue], index: usize) -> (usize, Fixn
|
||||
)
|
||||
}
|
||||
|
||||
fn push_var_eq_functors<'a>(
|
||||
heap: &mut Heap,
|
||||
iter: impl Iterator<Item = (&'a VarKey, &'a HeapCellValue)>,
|
||||
atom_tbl: &mut AtomTable,
|
||||
) -> Vec<HeapCellValue> {
|
||||
let mut list_of_var_eqs = vec![];
|
||||
|
||||
for (var, binding) in iter {
|
||||
let var_atom = atom_tbl.build_with(&var.to_string());
|
||||
let h = heap.len();
|
||||
|
||||
heap.push(atom_as_cell!(atom!("="), 2));
|
||||
heap.push(atom_as_cell!(var_atom));
|
||||
heap.push(*binding);
|
||||
|
||||
list_of_var_eqs.push(str_loc_as_cell!(h));
|
||||
}
|
||||
|
||||
list_of_var_eqs
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ball {
|
||||
pub(super) boundary: usize,
|
||||
@@ -481,6 +510,133 @@ impl MachineState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_read_term_options(
|
||||
&mut self,
|
||||
mut var_list: Vec<(VarKey, HeapCellValue, usize)>,
|
||||
singleton_var_list: Vec<HeapCellValue>,
|
||||
) -> CallResult {
|
||||
var_list.sort_by(|(_,_,idx_1),(_,_,idx_2)| idx_1.cmp(idx_2));
|
||||
|
||||
let list_of_var_eqs = push_var_eq_functors(
|
||||
&mut self.heap,
|
||||
var_list.iter().filter_map(|(var_name, var,_)| if var_name.is_anon() { None } else { Some((var_name,var)) }),
|
||||
&mut self.atom_tbl,
|
||||
);
|
||||
|
||||
let singleton_addr = self.registers[3];
|
||||
let singletons_offset = heap_loc_as_cell!(
|
||||
iter_to_heap_list(&mut self.heap, singleton_var_list.into_iter())
|
||||
);
|
||||
|
||||
unify_fn!(*self, singletons_offset, singleton_addr);
|
||||
|
||||
if self.fail {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let vars_addr = self.registers[4];
|
||||
let vars_offset = heap_loc_as_cell!(
|
||||
iter_to_heap_list(&mut self.heap, var_list.into_iter().map(|(_,cell,_)| cell))
|
||||
);
|
||||
|
||||
unify_fn!(*self, vars_offset, vars_addr);
|
||||
|
||||
if self.fail {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let var_names_addr = self.registers[5];
|
||||
let var_names_offset = heap_loc_as_cell!(
|
||||
iter_to_heap_list(&mut self.heap, list_of_var_eqs.into_iter())
|
||||
);
|
||||
|
||||
Ok(unify_fn!(*self, var_names_offset, var_names_addr))
|
||||
}
|
||||
|
||||
pub fn read_term_body(&mut self, mut term_write_result: TermWriteResult) -> CallResult {
|
||||
let heap_loc = read_heap_cell!(self.heap[term_write_result.heap_loc],
|
||||
(HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset) => {
|
||||
pstr_loc_as_cell!(term_write_result.heap_loc)
|
||||
}
|
||||
_ => {
|
||||
heap_loc_as_cell!(term_write_result.heap_loc)
|
||||
}
|
||||
);
|
||||
|
||||
unify_fn!(*self, heap_loc, self.registers[2]);
|
||||
|
||||
if self.fail {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for var in term_write_result.var_dict.values_mut() {
|
||||
*var = heap_bound_deref(&self.heap, *var);
|
||||
}
|
||||
|
||||
let mut singleton_var_set: IndexMap<Ref, bool> = IndexMap::new();
|
||||
|
||||
for cell in stackful_preorder_iter(&mut self.heap, &mut self.stack, heap_loc) {
|
||||
let cell = unmark_cell_bits!(cell);
|
||||
|
||||
if let Some(var) = cell.as_var() {
|
||||
if !singleton_var_set.contains_key(&var) {
|
||||
singleton_var_set.insert(var, true);
|
||||
} else {
|
||||
singleton_var_set.insert(var, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let singleton_var_list = push_var_eq_functors(
|
||||
&mut self.heap,
|
||||
term_write_result.var_dict.iter().filter(|(var_name, binding)| {
|
||||
if var_name.is_anon() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(r) = binding.as_var() {
|
||||
*singleton_var_set.get(&r).unwrap_or(&false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
&mut self.atom_tbl,
|
||||
);
|
||||
|
||||
for var in term_write_result.var_dict.values_mut() {
|
||||
*var = heap_bound_deref(&self.heap, *var);
|
||||
}
|
||||
|
||||
let mut var_list = Vec::with_capacity(singleton_var_set.len());
|
||||
|
||||
for (var_name, addr) in term_write_result.var_dict {
|
||||
if let Some(var) = addr.as_var() {
|
||||
if let Some(idx) = singleton_var_set.get_index_of(&var) {
|
||||
var_list.push((var_name, addr, idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.write_read_term_options(var_list, singleton_var_list)
|
||||
}
|
||||
|
||||
pub fn read_term_from_user_input_eof_handler(&mut self, stream: Stream) -> Result<OnEOF, MachineStub> {
|
||||
self.eof_action(
|
||||
self.registers[2],
|
||||
stream,
|
||||
atom!("read_term"),
|
||||
3,
|
||||
)?;
|
||||
|
||||
if stream.options().eof_action() == EOFAction::Reset {
|
||||
if self.fail == false {
|
||||
return Ok(OnEOF::Continue);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OnEOF::Return)
|
||||
}
|
||||
|
||||
// Safety: the atom_tbl lives for the lifetime of the machine, as does the helper, so the ptr
|
||||
// will always be valid.
|
||||
pub fn read_term_from_user_input(&mut self, stream: Stream, indices: &mut IndexStore) -> CallResult {
|
||||
@@ -490,40 +646,54 @@ impl MachineState {
|
||||
unsafe {
|
||||
let readline = ptr.as_ptr().as_mut().unwrap();
|
||||
readline.set_atoms_for_completion(atoms_ptr);
|
||||
let ret = self.read_term(stream, indices);
|
||||
return ret
|
||||
return self.read_term(
|
||||
stream,
|
||||
indices,
|
||||
MachineState::read_term_from_user_input_eof_handler,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Stream::Byte(_) = stream {
|
||||
return self.read_term(stream, indices)
|
||||
return self.read_term(
|
||||
stream,
|
||||
indices,
|
||||
MachineState::read_term_from_user_input_eof_handler
|
||||
)
|
||||
}
|
||||
|
||||
unreachable!("Stream must be a Stream::Readline(_)")
|
||||
}
|
||||
|
||||
pub fn read_term(&mut self, stream: Stream, indices: &mut IndexStore) -> CallResult {
|
||||
fn push_var_eq_functors<'a>(
|
||||
heap: &mut Heap,
|
||||
iter: impl Iterator<Item = (&'a Rc<String>, &'a HeapCellValue)>,
|
||||
atom_tbl: &mut AtomTable,
|
||||
) -> Vec<HeapCellValue> {
|
||||
let mut list_of_var_eqs = vec![];
|
||||
pub fn read_term_eof_handler(&mut self, mut stream: Stream) -> Result<OnEOF, MachineStub> {
|
||||
if stream.at_end_of_stream() {
|
||||
unify!(self, self.registers[2], atom_as_cell!(atom!("end_of_file")));
|
||||
stream.set_past_end_of_stream(true);
|
||||
return Ok(OnEOF::Return);
|
||||
} else if stream.past_end_of_stream() {
|
||||
self.eof_action(
|
||||
self.registers[2],
|
||||
stream,
|
||||
atom!("read_term"),
|
||||
3,
|
||||
)?;
|
||||
|
||||
for (var, binding) in iter {
|
||||
let var_atom = atom_tbl.build_with(&var);
|
||||
let h = heap.len();
|
||||
|
||||
heap.push(atom_as_cell!(atom!("="), 2));
|
||||
heap.push(atom_as_cell!(var_atom));
|
||||
heap.push(*binding);
|
||||
|
||||
list_of_var_eqs.push(str_loc_as_cell!(h));
|
||||
if stream.options().eof_action() == EOFAction::Reset {
|
||||
if self.fail == false {
|
||||
return Ok(OnEOF::Continue);
|
||||
}
|
||||
}
|
||||
|
||||
list_of_var_eqs
|
||||
}
|
||||
|
||||
Ok(OnEOF::Return)
|
||||
}
|
||||
|
||||
pub fn read_term(
|
||||
&mut self,
|
||||
stream: Stream,
|
||||
indices: &mut IndexStore,
|
||||
eof_handler: impl Fn(&mut Self, Stream) -> Result<OnEOF, MachineStub>,
|
||||
) -> CallResult {
|
||||
self.check_stream_properties(
|
||||
stream,
|
||||
StreamType::Text,
|
||||
@@ -542,116 +712,16 @@ impl MachineState {
|
||||
|
||||
loop {
|
||||
match self.read(stream, &indices.op_dir) {
|
||||
Ok(mut term_write_result) => {
|
||||
let heap_loc = read_heap_cell!(self.heap[term_write_result.heap_loc],
|
||||
(HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset) => {
|
||||
pstr_loc_as_cell!(term_write_result.heap_loc)
|
||||
}
|
||||
_ => {
|
||||
heap_loc_as_cell!(term_write_result.heap_loc)
|
||||
}
|
||||
);
|
||||
|
||||
let term = self.registers[2];
|
||||
unify_fn!(*self, heap_loc, term);
|
||||
let term = heap_loc;
|
||||
|
||||
if self.fail {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut singleton_var_set: IndexMap<Ref, bool> = IndexMap::new();
|
||||
|
||||
for addr in stackful_preorder_iter(&mut self.heap, term) {
|
||||
let addr = unmark_cell_bits!(addr);
|
||||
|
||||
if let Some(var) = addr.as_var() {
|
||||
if !singleton_var_set.contains_key(&var) {
|
||||
singleton_var_set.insert(var, true);
|
||||
} else {
|
||||
singleton_var_set.insert(var, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for var in term_write_result.var_dict.values_mut() {
|
||||
*var = heap_bound_deref(&self.heap, *var);
|
||||
}
|
||||
|
||||
let singleton_var_list = push_var_eq_functors(
|
||||
&mut self.heap,
|
||||
term_write_result.var_dict.iter().filter(|(_, binding)| {
|
||||
if let Some(r) = binding.as_var() {
|
||||
*singleton_var_set.get(&r).unwrap_or(&false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
&mut self.atom_tbl,
|
||||
);
|
||||
|
||||
let mut var_list = Vec::with_capacity(singleton_var_set.len());
|
||||
|
||||
for (var_name, addr) in term_write_result.var_dict {
|
||||
if let Some(var) = addr.as_var() {
|
||||
let idx = singleton_var_set.get_index_of(&var).unwrap();
|
||||
var_list.push((var_name, addr, idx));
|
||||
}
|
||||
}
|
||||
|
||||
var_list.sort_by(|(_,_,idx_1),(_,_,idx_2)| idx_1.cmp(idx_2));
|
||||
|
||||
let list_of_var_eqs = push_var_eq_functors(
|
||||
&mut self.heap,
|
||||
var_list.iter().map(|(var_name, var,_)| (var_name,var)),
|
||||
&mut self.atom_tbl,
|
||||
);
|
||||
|
||||
let singleton_addr = self.registers[3];
|
||||
let singletons_offset = heap_loc_as_cell!(
|
||||
iter_to_heap_list(&mut self.heap, singleton_var_list.into_iter())
|
||||
);
|
||||
|
||||
unify_fn!(*self, singletons_offset, singleton_addr);
|
||||
|
||||
if self.fail {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let vars_addr = self.registers[4];
|
||||
let vars_offset = heap_loc_as_cell!(
|
||||
iter_to_heap_list(&mut self.heap, var_list.into_iter().map(|(_,cell,_)| cell))
|
||||
);
|
||||
|
||||
unify_fn!(*self, vars_offset, vars_addr);
|
||||
|
||||
if self.fail {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let var_names_addr = self.registers[5];
|
||||
let var_names_offset = heap_loc_as_cell!(
|
||||
iter_to_heap_list(&mut self.heap, list_of_var_eqs.into_iter())
|
||||
);
|
||||
|
||||
return Ok(unify_fn!(*self, var_names_offset, var_names_addr));
|
||||
}
|
||||
Ok(term_write_result) => return self.read_term_body(term_write_result),
|
||||
Err(err) => {
|
||||
if let CompilationError::ParserError(ParserError::UnexpectedEOF) = err {
|
||||
self.eof_action(
|
||||
self.registers[2],
|
||||
stream,
|
||||
atom!("read_term"),
|
||||
3,
|
||||
)?;
|
||||
|
||||
if stream.options().eof_action() == EOFAction::Reset {
|
||||
if self.fail == false {
|
||||
continue;
|
||||
match &err {
|
||||
CompilationError::ParserError(e) if e.is_unexpected_eof() => {
|
||||
match eof_handler(self, stream)? {
|
||||
OnEOF::Return => return self.write_read_term_options(vec![], vec![]),
|
||||
OnEOF::Continue => continue,
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let stub = functor_stub(atom!("read_term"), 3);
|
||||
@@ -671,13 +741,14 @@ impl MachineState {
|
||||
let numbervars = self.store(self.deref(self.registers[4]));
|
||||
let quoted = self.store(self.deref(self.registers[5]));
|
||||
let max_depth = self.store(self.deref(self.registers[7]));
|
||||
let double_quotes = self.store(self.deref(self.registers[8]));
|
||||
|
||||
let term_to_be_printed = self.store(self.deref(self.registers[2]));
|
||||
let stub_gen = || functor_stub(atom!("write_term"), 2);
|
||||
|
||||
let printer = match self.try_from_list(self.registers[6], stub_gen) {
|
||||
Ok(addrs) => {
|
||||
let mut var_names: IndexMap<HeapCellValue, Rc<String>> = IndexMap::new();
|
||||
let mut var_names: IndexMap<HeapCellValue, VarPtr> = IndexMap::new();
|
||||
|
||||
for addr in addrs {
|
||||
read_heap_cell!(addr,
|
||||
@@ -695,18 +766,18 @@ impl MachineState {
|
||||
|
||||
read_heap_cell!(atom,
|
||||
(HeapCellValueTag::Char, c) => {
|
||||
var_names.insert(var, Rc::new(c.to_string()));
|
||||
var_names.insert(var, VarPtr::from(c.to_string()));
|
||||
}
|
||||
(HeapCellValueTag::Atom, (name, _arity)) => {
|
||||
debug_assert_eq!(_arity, 0);
|
||||
var_names.insert(var, Rc::new(name.as_str().to_owned()));
|
||||
var_names.insert(var, VarPtr::from(name.as_str()));
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
var_names.insert(var, Rc::new(name.as_str().to_owned()));
|
||||
var_names.insert(var, VarPtr::from(name.as_str()));
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
@@ -752,7 +823,25 @@ impl MachineState {
|
||||
);
|
||||
|
||||
let quoted = read_heap_cell!(quoted,
|
||||
(HeapCellValueTag::Atom, (name, _arity)) => {
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
);
|
||||
|
||||
let double_quotes = read_heap_cell!(double_quotes,
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
@@ -769,6 +858,8 @@ impl MachineState {
|
||||
|
||||
let mut printer = HCPrinter::new(
|
||||
&mut self.heap,
|
||||
&mut self.atom_tbl,
|
||||
&mut self.stack,
|
||||
op_dir,
|
||||
PrinterOutputter::new(),
|
||||
term_to_be_printed,
|
||||
@@ -777,6 +868,7 @@ impl MachineState {
|
||||
printer.ignore_ops = ignore_ops;
|
||||
printer.numbervars = numbervars;
|
||||
printer.quoted = quoted;
|
||||
printer.double_quotes = double_quotes;
|
||||
|
||||
match Number::try_from(max_depth) {
|
||||
Ok(Number::Fixnum(n)) => {
|
||||
@@ -824,7 +916,7 @@ impl MachineState {
|
||||
let b = self.b;
|
||||
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Fixnum, b0) => {
|
||||
(HeapCellValueTag::CutPoint, b0) => {
|
||||
let b0 = b0.get_num() as usize;
|
||||
|
||||
if b > b0 {
|
||||
@@ -836,63 +928,6 @@ impl MachineState {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn try_me_else(&mut self, offset: usize) {
|
||||
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 + offset;
|
||||
or_frame.prelude.boip = 0;
|
||||
or_frame.prelude.biip = 0;
|
||||
or_frame.prelude.tr = self.tr;
|
||||
or_frame.prelude.h = self.heap.len();
|
||||
or_frame.prelude.b0 = self.b0;
|
||||
|
||||
self.b = b;
|
||||
|
||||
for i in 0..n {
|
||||
or_frame[i] = self.registers[i+1];
|
||||
}
|
||||
|
||||
self.hb = self.heap.len();
|
||||
self.p += 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn indexed_try(&mut self, offset: usize) {
|
||||
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; // + 1; in self.iip now!
|
||||
or_frame.prelude.boip = self.oip;
|
||||
or_frame.prelude.biip = self.iip + 1;
|
||||
or_frame.prelude.tr = self.tr;
|
||||
or_frame.prelude.h = self.heap.len();
|
||||
or_frame.prelude.b0 = self.b0;
|
||||
|
||||
self.b = b;
|
||||
|
||||
for i in 0..n {
|
||||
or_frame[i] = self.registers[i+1];
|
||||
}
|
||||
|
||||
self.hb = self.heap.len();
|
||||
self.p = self.p + offset;
|
||||
|
||||
self.oip = 0;
|
||||
self.iip = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
+117
-1169
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,8 @@ impl MockWAM {
|
||||
|
||||
let mut printer = HCPrinter::new(
|
||||
&mut self.machine_st.heap,
|
||||
&mut self.machine_st.atom_tbl,
|
||||
&mut self.machine_st.stack,
|
||||
&self.op_dir,
|
||||
PrinterOutputter::new(),
|
||||
heap_loc_as_cell!(term_write_result.heap_loc),
|
||||
@@ -69,7 +71,12 @@ impl MockWAM {
|
||||
printer.var_names = term_write_result
|
||||
.var_dict
|
||||
.into_iter()
|
||||
.map(|(var, cell)| (cell, var))
|
||||
.map(|(var, cell)| {
|
||||
match var {
|
||||
VarKey::VarPtr(var) => (cell, var.clone()),
|
||||
VarKey::AnonVar(_) => (cell, VarPtr::from(var.to_string()))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(printer.print().result())
|
||||
|
||||
+493
-129
@@ -19,16 +19,19 @@ pub mod machine_state_impl;
|
||||
pub mod mock_wam;
|
||||
pub mod parsed_results;
|
||||
pub mod partial_string;
|
||||
pub mod disjuncts;
|
||||
pub mod preprocessor;
|
||||
pub mod stack;
|
||||
pub mod streams;
|
||||
pub mod system_calls;
|
||||
pub mod term_stream;
|
||||
pub mod unify;
|
||||
|
||||
use crate::arena::*;
|
||||
use crate::arithmetic::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::forms::*;
|
||||
use crate::ffi::ForeignFunctionTable;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::args::*;
|
||||
use crate::machine::compile::*;
|
||||
@@ -41,7 +44,7 @@ use crate::machine::machine_state::*;
|
||||
use crate::machine::stack::*;
|
||||
use crate::machine::streams::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use crate::types::*;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
@@ -70,6 +73,7 @@ pub struct Machine {
|
||||
pub(super) user_output: Stream,
|
||||
pub(super) user_error: Stream,
|
||||
pub(super) load_contexts: Vec<LoadContext>,
|
||||
pub(super) foreign_function_table: ForeignFunctionTable,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -205,7 +209,7 @@ impl Machine {
|
||||
self.machine_st.throw_exception(err);
|
||||
}
|
||||
|
||||
fn run_module_predicate(&mut self, module_name: Atom, key: PredicateKey) {
|
||||
fn run_module_predicate(&mut self, module_name: Atom, key: PredicateKey) -> std::process::ExitCode {
|
||||
if let Some(module) = self.indices.modules.get(&module_name) {
|
||||
if let Some(ref code_index) = module.code_dir.get(&key) {
|
||||
let p = code_index.local().unwrap();
|
||||
@@ -255,31 +259,22 @@ impl Machine {
|
||||
let mut path_buf = current_dir();
|
||||
path_buf.push("machine/attributed_variables.pl");
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
include_str!("attributed_variables.pl"),
|
||||
&mut self.machine_st.arena,
|
||||
),
|
||||
self,
|
||||
ListingSource::from_file_and_path(
|
||||
atom!("attributed_variables"),
|
||||
path_buf,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let stream = Stream::from_static_string(
|
||||
include_str!("attributed_variables.pl"),
|
||||
&mut self.machine_st.arena,
|
||||
);
|
||||
|
||||
self.load_file(path_buf.to_str().unwrap(), stream);
|
||||
|
||||
let mut path_buf = current_dir();
|
||||
path_buf.push("machine/project_attributes.pl");
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
include_str!("project_attributes.pl"),
|
||||
&mut self.machine_st.arena,
|
||||
),
|
||||
self,
|
||||
ListingSource::from_file_and_path(atom!("project_attributes"), path_buf),
|
||||
)
|
||||
.unwrap();
|
||||
let stream = Stream::from_static_string(
|
||||
include_str!("project_attributes.pl"),
|
||||
&mut self.machine_st.arena,
|
||||
);
|
||||
|
||||
self.load_file(path_buf.to_str().unwrap(), stream);
|
||||
|
||||
if let Some(module) = self.indices.modules.get(&atom!("$atts")) {
|
||||
if let Some(code_index) = module.code_dir.get(&(atom!("driver"), 2)) {
|
||||
@@ -288,7 +283,7 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_top_level(&mut self, module_name: Atom, key: PredicateKey) {
|
||||
pub fn run_top_level(&mut self, module_name: Atom, key: PredicateKey) -> std::process::ExitCode {
|
||||
let mut arg_pstrs = vec![];
|
||||
|
||||
for arg in env::args() {
|
||||
@@ -304,7 +299,7 @@ impl Machine {
|
||||
arg_pstrs.into_iter()
|
||||
));
|
||||
|
||||
self.run_module_predicate(module_name, key);
|
||||
self.run_module_predicate(module_name, key)
|
||||
}
|
||||
|
||||
pub fn set_user_input(&mut self, input: String) {
|
||||
@@ -380,46 +375,45 @@ impl Machine {
|
||||
Instruction::BreakFromDispatchLoop,
|
||||
Instruction::InstallVerifyAttr,
|
||||
Instruction::VerifyAttrInterrupt,
|
||||
Instruction::ExecuteTermGreaterThan(0),
|
||||
Instruction::ExecuteTermLessThan(0),
|
||||
Instruction::ExecuteTermGreaterThanOrEqual(0),
|
||||
Instruction::ExecuteTermLessThanOrEqual(0),
|
||||
Instruction::ExecuteTermEqual(0),
|
||||
Instruction::ExecuteTermNotEqual(0),
|
||||
Instruction::ExecuteNumberGreaterThan(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteNumberLessThan(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteNumberGreaterThanOrEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteNumberLessThanOrEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteNumberEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteNumberNotEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteIs(temp_v!(1), ar_reg!(temp_v!(2)), 0),
|
||||
Instruction::ExecuteAcyclicTerm(0),
|
||||
Instruction::ExecuteArg(0),
|
||||
Instruction::ExecuteCompare(0),
|
||||
Instruction::ExecuteCopyTerm(0),
|
||||
Instruction::ExecuteFunctor(0),
|
||||
Instruction::ExecuteGround(0),
|
||||
Instruction::ExecuteKeySort(0),
|
||||
Instruction::ExecuteRead(0),
|
||||
Instruction::ExecuteSort(0),
|
||||
Instruction::ExecuteN(1, 0),
|
||||
Instruction::ExecuteN(2, 0),
|
||||
Instruction::ExecuteN(3, 0),
|
||||
Instruction::ExecuteN(4, 0),
|
||||
Instruction::ExecuteN(5, 0),
|
||||
Instruction::ExecuteN(6, 0),
|
||||
Instruction::ExecuteN(7, 0),
|
||||
Instruction::ExecuteN(8, 0),
|
||||
Instruction::ExecuteN(9, 0),
|
||||
Instruction::ExecuteIsAtom(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsAtomic(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsCompound(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsInteger(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsNumber(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsRational(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsFloat(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsNonVar(temp_v!(1), 0),
|
||||
Instruction::ExecuteIsVar(temp_v!(1), 0)
|
||||
Instruction::ExecuteTermGreaterThan,
|
||||
Instruction::ExecuteTermLessThan,
|
||||
Instruction::ExecuteTermGreaterThanOrEqual,
|
||||
Instruction::ExecuteTermLessThanOrEqual,
|
||||
Instruction::ExecuteTermEqual,
|
||||
Instruction::ExecuteTermNotEqual,
|
||||
Instruction::ExecuteNumberGreaterThan(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteNumberLessThan(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteNumberGreaterThanOrEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteNumberLessThanOrEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteNumberEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteNumberNotEqual(ar_reg!(temp_v!(1)), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteIs(temp_v!(1), ar_reg!(temp_v!(2))),
|
||||
Instruction::ExecuteAcyclicTerm,
|
||||
Instruction::ExecuteArg,
|
||||
Instruction::ExecuteCompare,
|
||||
Instruction::ExecuteCopyTerm,
|
||||
Instruction::ExecuteFunctor,
|
||||
Instruction::ExecuteGround,
|
||||
Instruction::ExecuteKeySort,
|
||||
Instruction::ExecuteSort,
|
||||
Instruction::ExecuteN(1),
|
||||
Instruction::ExecuteN(2),
|
||||
Instruction::ExecuteN(3),
|
||||
Instruction::ExecuteN(4),
|
||||
Instruction::ExecuteN(5),
|
||||
Instruction::ExecuteN(6),
|
||||
Instruction::ExecuteN(7),
|
||||
Instruction::ExecuteN(8),
|
||||
Instruction::ExecuteN(9),
|
||||
Instruction::ExecuteIsAtom(temp_v!(1)),
|
||||
Instruction::ExecuteIsAtomic(temp_v!(1)),
|
||||
Instruction::ExecuteIsCompound(temp_v!(1)),
|
||||
Instruction::ExecuteIsInteger(temp_v!(1)),
|
||||
Instruction::ExecuteIsNumber(temp_v!(1)),
|
||||
Instruction::ExecuteIsRational(temp_v!(1)),
|
||||
Instruction::ExecuteIsFloat(temp_v!(1)),
|
||||
Instruction::ExecuteIsNonVar(temp_v!(1)),
|
||||
Instruction::ExecuteIsVar(temp_v!(1))
|
||||
].into_iter());
|
||||
|
||||
for (p, instr) in self.code[impls_offset ..].iter().enumerate() {
|
||||
@@ -458,6 +452,7 @@ impl Machine {
|
||||
user_output,
|
||||
user_error,
|
||||
load_contexts: vec![],
|
||||
foreign_function_table: Default::default(),
|
||||
};
|
||||
|
||||
let mut lib_path = current_dir();
|
||||
@@ -566,103 +561,436 @@ impl Machine {
|
||||
self.machine_st.verify_attr_interrupt(p, arity);
|
||||
}
|
||||
|
||||
fn next_clause_applicable(&mut self, mut offset: usize) -> bool {
|
||||
loop {
|
||||
match &self.code[offset] {
|
||||
Instruction::IndexingCode(indexing_lines) => {
|
||||
let mut oip = 0;
|
||||
let mut cell = empty_list_as_cell!();
|
||||
|
||||
loop {
|
||||
let indexing_code_ptr = match &indexing_lines[oip] {
|
||||
&IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(arg, v, c, l, s)) => {
|
||||
cell = self.deref_register(arg);
|
||||
self.machine_st.select_switch_on_term_index(cell, v, c, l, s)
|
||||
}
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(hm)) => {
|
||||
let lit = self.machine_st.constant_to_literal(cell);
|
||||
hm.get(&lit).cloned().unwrap_or(IndexingCodePtr::Fail)
|
||||
}
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(hm)) => {
|
||||
self.machine_st.select_switch_on_structure_index(cell, hm)
|
||||
}
|
||||
_ => {
|
||||
offset += 1;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match indexing_code_ptr {
|
||||
IndexingCodePtr::External(_) | IndexingCodePtr::DynamicExternal(_) => {
|
||||
offset += 1;
|
||||
break;
|
||||
}
|
||||
IndexingCodePtr::Internal(i) => oip += i,
|
||||
IndexingCodePtr::Fail => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
&Instruction::GetConstant(Level::Shallow, lit, RegType::Temp(t)) => {
|
||||
let cell = self.deref_register(t);
|
||||
|
||||
if cell.is_var() {
|
||||
offset += 1;
|
||||
} else if lit.get_tag() == HeapCellValueTag::CStr {
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::CStr) => {
|
||||
if cell == lit {
|
||||
offset += 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc) => {
|
||||
offset += 1;
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.machine_st.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
if name == atom!(".") && arity == 2 {
|
||||
offset += 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
self.machine_st.write_literal_to_var(cell, lit);
|
||||
|
||||
if self.machine_st.fail {
|
||||
self.machine_st.fail = false;
|
||||
return false;
|
||||
} else {
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&Instruction::GetList(Level::Shallow, RegType::Temp(t)) => {
|
||||
let cell = self.deref_register(t);
|
||||
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc | HeapCellValueTag::CStr) => {
|
||||
offset += 1;
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.machine_st.heap[s]).get_name_and_arity();
|
||||
|
||||
if name == atom!(".") && arity == 2 {
|
||||
offset += 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var | HeapCellValueTag::StackVar) => {
|
||||
offset += 1;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
&Instruction::GetStructure(Level::Shallow, name, arity, RegType::Temp(t)) => {
|
||||
let cell = self.deref_register(t);
|
||||
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
if (name, arity) == cell_as_atom_cell!(self.machine_st.heap[s]).get_name_and_arity() {
|
||||
offset += 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var | HeapCellValueTag::StackVar) => {
|
||||
offset += 1;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
&Instruction::GetPartialString(Level::Shallow, string, RegType::Temp(t), has_tail) => {
|
||||
let cell = self.deref_register(t);
|
||||
|
||||
read_heap_cell!(cell,
|
||||
(HeapCellValueTag::CStr, cstr) => {
|
||||
if !has_tail && string != cstr {
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += 1;
|
||||
}
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc) => {
|
||||
offset += 1;
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.machine_st.heap[s]).get_name_and_arity();
|
||||
|
||||
if name == atom!(".") && arity == 2 {
|
||||
offset += 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var | HeapCellValueTag::StackVar) => {
|
||||
offset += 1;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
Instruction::GetConstant(..) |
|
||||
Instruction::GetList(..) |
|
||||
Instruction::GetStructure(..) |
|
||||
Instruction::GetPartialString(..) |
|
||||
&Instruction::UnifyVoid(..) |
|
||||
&Instruction::UnifyConstant(..) |
|
||||
&Instruction::GetVariable(..) |
|
||||
&Instruction::GetValue(..) |
|
||||
&Instruction::UnifyVariable(..) |
|
||||
&Instruction::UnifyValue(..) |
|
||||
&Instruction::UnifyLocalValue(..) => {
|
||||
offset += 1;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn next_applicable_clause(&mut self, mut offset: usize) -> Option<usize> {
|
||||
while !self.next_clause_applicable(self.machine_st.p + offset + 1) {
|
||||
match &self.code[self.machine_st.p + offset] {
|
||||
&Instruction::DefaultRetryMeElse(o) | &Instruction::RetryMeElse(o) |
|
||||
&Instruction::DynamicElse(.., NextOrFail::Next(o)) |
|
||||
&Instruction::DynamicInternalElse(.., NextOrFail::Next(o)) => offset += o,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(offset)
|
||||
}
|
||||
|
||||
fn next_inner_applicable_clause(&mut self) -> Option<u32> {
|
||||
let mut inner_offset = 1u32;
|
||||
|
||||
loop {
|
||||
match &self.code[self.machine_st.p] {
|
||||
Instruction::IndexingCode(indexing_lines) => {
|
||||
match &indexing_lines[self.machine_st.oip as usize] {
|
||||
IndexingLine::IndexedChoice(indexed_choice) => {
|
||||
match &indexed_choice[(self.machine_st.iip + inner_offset) as usize] {
|
||||
&IndexedChoiceInstruction::Retry(o) => {
|
||||
if self.next_clause_applicable(self.machine_st.p + o) {
|
||||
return Some(inner_offset);
|
||||
}
|
||||
|
||||
inner_offset += 1;
|
||||
}
|
||||
&IndexedChoiceInstruction::Trust(o) => {
|
||||
return if self.next_clause_applicable(self.machine_st.p + o) {
|
||||
Some(inner_offset)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
IndexingLine::DynamicIndexedChoice(indexed_choice) => {
|
||||
let idx = (self.machine_st.iip + inner_offset) as usize;
|
||||
let o = indexed_choice[idx];
|
||||
|
||||
if idx + 1 == indexed_choice.len() {
|
||||
return if self.next_clause_applicable(self.machine_st.p + o) {
|
||||
Some(inner_offset)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
} else {
|
||||
if self.next_clause_applicable(self.machine_st.p + o) {
|
||||
return Some(inner_offset);
|
||||
}
|
||||
|
||||
inner_offset += 1;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn try_me_else(&mut self, offset: usize) {
|
||||
if let Some(offset) = self.next_applicable_clause(offset) {
|
||||
let n = self.machine_st.num_of_args;
|
||||
let b = self.machine_st.stack.allocate_or_frame(n);
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
|
||||
|
||||
or_frame.prelude.num_cells = n;
|
||||
or_frame.prelude.e = self.machine_st.e;
|
||||
or_frame.prelude.cp = self.machine_st.cp;
|
||||
or_frame.prelude.b = self.machine_st.b;
|
||||
or_frame.prelude.bp = self.machine_st.p + offset;
|
||||
or_frame.prelude.boip = 0;
|
||||
or_frame.prelude.biip = 0;
|
||||
or_frame.prelude.tr = self.machine_st.tr;
|
||||
or_frame.prelude.h = self.machine_st.heap.len();
|
||||
or_frame.prelude.b0 = self.machine_st.b0;
|
||||
or_frame.prelude.attr_var_queue_len = self.machine_st.attr_var_init.attr_var_queue.len();
|
||||
|
||||
self.machine_st.b = b;
|
||||
|
||||
for i in 0..n {
|
||||
or_frame[i] = self.machine_st.registers[i+1];
|
||||
}
|
||||
|
||||
self.machine_st.hb = self.machine_st.heap.len();
|
||||
}
|
||||
|
||||
self.machine_st.p += 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn indexed_try(&mut self, offset: usize) {
|
||||
if let Some(iip_offset) = self.next_inner_applicable_clause() {
|
||||
let n = self.machine_st.num_of_args;
|
||||
let b = self.machine_st.stack.allocate_or_frame(n);
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
|
||||
|
||||
or_frame.prelude.num_cells = n;
|
||||
or_frame.prelude.e = self.machine_st.e;
|
||||
or_frame.prelude.cp = self.machine_st.cp;
|
||||
or_frame.prelude.b = self.machine_st.b;
|
||||
or_frame.prelude.bp = self.machine_st.p;
|
||||
or_frame.prelude.boip = self.machine_st.oip;
|
||||
or_frame.prelude.biip = self.machine_st.iip + iip_offset; // 1
|
||||
or_frame.prelude.tr = self.machine_st.tr;
|
||||
or_frame.prelude.h = self.machine_st.heap.len();
|
||||
or_frame.prelude.b0 = self.machine_st.b0;
|
||||
or_frame.prelude.attr_var_queue_len = self.machine_st.attr_var_init.attr_var_queue.len();
|
||||
|
||||
self.machine_st.b = b;
|
||||
|
||||
for i in 0..n {
|
||||
or_frame[i] = self.machine_st.registers[i+1];
|
||||
}
|
||||
|
||||
self.machine_st.hb = self.machine_st.heap.len();
|
||||
|
||||
self.machine_st.oip = 0;
|
||||
self.machine_st.iip = 0;
|
||||
}
|
||||
|
||||
self.machine_st.p += offset;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn retry_me_else(&mut self, offset: usize) {
|
||||
let b = self.machine_st.b;
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
|
||||
let n = or_frame.prelude.univ_prelude.num_cells;
|
||||
let n = or_frame.prelude.num_cells;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
|
||||
for i in 0..n {
|
||||
self.machine_st.registers[i + 1] = or_frame[i];
|
||||
}
|
||||
|
||||
self.machine_st.num_of_args = n;
|
||||
self.machine_st.e = or_frame.prelude.e;
|
||||
self.machine_st.cp = or_frame.prelude.cp;
|
||||
|
||||
or_frame.prelude.bp = self.machine_st.p + offset;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
let target_h = or_frame.prelude.h;
|
||||
|
||||
self.machine_st.tr = or_frame.prelude.tr;
|
||||
|
||||
self.reset_attr_var_state();
|
||||
self.machine_st.hb = target_h;
|
||||
|
||||
self.unwind_trail(old_tr, curr_tr);
|
||||
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
self.machine_st.heap.truncate(target_h);
|
||||
if let Some(offset) = self.next_applicable_clause(offset) {
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
|
||||
|
||||
self.machine_st.p += 1;
|
||||
self.machine_st.num_of_args = n;
|
||||
self.machine_st.e = or_frame.prelude.e;
|
||||
self.machine_st.cp = or_frame.prelude.cp;
|
||||
|
||||
or_frame.prelude.bp = self.machine_st.p + offset;
|
||||
|
||||
let target_h = or_frame.prelude.h;
|
||||
let attr_var_queue_len = or_frame.prelude.attr_var_queue_len;
|
||||
|
||||
self.machine_st.tr = or_frame.prelude.tr;
|
||||
self.reset_attr_var_state(attr_var_queue_len);
|
||||
|
||||
self.machine_st.hb = target_h;
|
||||
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
self.machine_st.heap.truncate(target_h);
|
||||
|
||||
self.machine_st.p += 1;
|
||||
} else {
|
||||
self.trust_me_epilogue();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn retry(&mut self, offset: usize) {
|
||||
let b = self.machine_st.b;
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
|
||||
let n = or_frame.prelude.univ_prelude.num_cells;
|
||||
let n = or_frame.prelude.num_cells;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
|
||||
for i in 0..n {
|
||||
self.machine_st.registers[i+1] = or_frame[i];
|
||||
}
|
||||
|
||||
self.machine_st.num_of_args = n;
|
||||
self.machine_st.e = or_frame.prelude.e;
|
||||
self.machine_st.cp = or_frame.prelude.cp;
|
||||
|
||||
or_frame.prelude.biip += 1;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
let target_h = or_frame.prelude.h;
|
||||
|
||||
self.machine_st.tr = or_frame.prelude.tr;
|
||||
self.reset_attr_var_state();
|
||||
|
||||
self.machine_st.hb = target_h;
|
||||
self.machine_st.p = self.machine_st.p + offset;
|
||||
|
||||
self.unwind_trail(old_tr, curr_tr);
|
||||
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
self.machine_st.heap.truncate(target_h);
|
||||
if let Some(iip_offset) = self.next_inner_applicable_clause() {
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
|
||||
|
||||
self.machine_st.oip = 0;
|
||||
self.machine_st.iip = 0;
|
||||
self.machine_st.num_of_args = n;
|
||||
self.machine_st.e = or_frame.prelude.e;
|
||||
self.machine_st.cp = or_frame.prelude.cp;
|
||||
|
||||
or_frame.prelude.biip += iip_offset;
|
||||
|
||||
let target_h = or_frame.prelude.h;
|
||||
let attr_var_queue_len = or_frame.prelude.attr_var_queue_len;
|
||||
|
||||
self.machine_st.tr = or_frame.prelude.tr;
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
|
||||
self.reset_attr_var_state(attr_var_queue_len);
|
||||
|
||||
self.machine_st.hb = target_h;
|
||||
self.machine_st.p += offset;
|
||||
|
||||
self.machine_st.heap.truncate(target_h);
|
||||
|
||||
self.machine_st.oip = 0;
|
||||
self.machine_st.iip = 0;
|
||||
} else {
|
||||
self.trust_epilogue(offset);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn trust(&mut self, offset: usize) {
|
||||
let b = self.machine_st.b;
|
||||
let or_frame = self.machine_st.stack.index_or_frame(b);
|
||||
let n = or_frame.prelude.univ_prelude.num_cells;
|
||||
let n = or_frame.prelude.num_cells;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
|
||||
for i in 0..n {
|
||||
self.machine_st.registers[i+1] = or_frame[i];
|
||||
}
|
||||
|
||||
self.unwind_trail(old_tr, curr_tr);
|
||||
self.trust_epilogue(offset);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn trust_epilogue(&mut self, offset: usize) {
|
||||
let b = self.machine_st.b;
|
||||
let or_frame = self.machine_st.stack.index_or_frame(b);
|
||||
let n = or_frame.prelude.num_cells;
|
||||
|
||||
self.machine_st.num_of_args = n;
|
||||
self.machine_st.e = or_frame.prelude.e;
|
||||
self.machine_st.cp = or_frame.prelude.cp;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
let target_h = or_frame.prelude.h;
|
||||
|
||||
self.machine_st.tr = or_frame.prelude.tr;
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
|
||||
self.machine_st.b = or_frame.prelude.b;
|
||||
|
||||
self.reset_attr_var_state();
|
||||
self.reset_attr_var_state(or_frame.prelude.attr_var_queue_len);
|
||||
|
||||
self.machine_st.hb = target_h;
|
||||
self.machine_st.p = self.machine_st.p + offset;
|
||||
|
||||
self.unwind_trail(old_tr, curr_tr);
|
||||
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
self.machine_st.stack.truncate(b);
|
||||
self.machine_st.heap.truncate(target_h);
|
||||
|
||||
@@ -674,35 +1002,63 @@ impl Machine {
|
||||
fn trust_me(&mut self) {
|
||||
let b = self.machine_st.b;
|
||||
let or_frame = self.machine_st.stack.index_or_frame(b);
|
||||
let n = or_frame.prelude.univ_prelude.num_cells;
|
||||
let n = or_frame.prelude.num_cells;
|
||||
|
||||
for i in 0..n {
|
||||
self.machine_st.registers[i+1] = or_frame[i];
|
||||
}
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
|
||||
self.unwind_trail(old_tr, curr_tr);
|
||||
|
||||
self.trust_me_epilogue();
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn trust_me_epilogue(&mut self) {
|
||||
let b = self.machine_st.b;
|
||||
let or_frame = self.machine_st.stack.index_or_frame(b);
|
||||
let n = or_frame.prelude.num_cells;
|
||||
|
||||
self.machine_st.num_of_args = n;
|
||||
self.machine_st.e = or_frame.prelude.e;
|
||||
self.machine_st.cp = or_frame.prelude.cp;
|
||||
|
||||
let old_tr = or_frame.prelude.tr;
|
||||
let curr_tr = self.machine_st.tr;
|
||||
let target_h = or_frame.prelude.h;
|
||||
|
||||
self.machine_st.tr = or_frame.prelude.tr;
|
||||
self.machine_st.b = or_frame.prelude.b;
|
||||
|
||||
self.reset_attr_var_state();
|
||||
self.reset_attr_var_state(or_frame.prelude.attr_var_queue_len);
|
||||
|
||||
self.machine_st.hb = target_h;
|
||||
self.machine_st.p += 1;
|
||||
|
||||
self.unwind_trail(old_tr, curr_tr);
|
||||
|
||||
self.machine_st.trail.truncate(self.machine_st.tr);
|
||||
self.machine_st.stack.truncate(b);
|
||||
self.machine_st.heap.truncate(target_h);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn undefined_procedure(&mut self, name: Atom, arity: usize) -> CallResult {
|
||||
match self.machine_st.flags.unknown {
|
||||
Unknown::Error => {
|
||||
Err(self.machine_st.throw_undefined_error(name, arity))
|
||||
}
|
||||
Unknown::Fail => {
|
||||
self.machine_st.fail = true;
|
||||
Ok(())
|
||||
}
|
||||
Unknown::Warn => {
|
||||
println!("warning: predicate {}/{} is undefined", name.as_str(), arity);
|
||||
self.machine_st.fail = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_call(&mut self, name: Atom, arity: usize, idx: IndexPtr) -> CallResult {
|
||||
let compiled_tl_index = idx.p() as usize;
|
||||
@@ -712,7 +1068,7 @@ impl Machine {
|
||||
self.machine_st.fail = true;
|
||||
}
|
||||
IndexPtrTag::Undefined => {
|
||||
return Err(self.machine_st.throw_undefined_error(name, arity));
|
||||
return self.undefined_procedure(name, arity);
|
||||
}
|
||||
IndexPtrTag::DynamicIndex => {
|
||||
self.machine_st.dynamic_mode = FirstOrNext::First;
|
||||
@@ -735,7 +1091,7 @@ impl Machine {
|
||||
self.machine_st.fail = true;
|
||||
}
|
||||
IndexPtrTag::Undefined => {
|
||||
return Err(self.machine_st.throw_undefined_error(name, arity));
|
||||
return self.undefined_procedure(name, arity);
|
||||
}
|
||||
IndexPtrTag::DynamicIndex => {
|
||||
self.machine_st.dynamic_mode = FirstOrNext::First;
|
||||
@@ -764,7 +1120,7 @@ impl Machine {
|
||||
if let Some(idx) = module.code_dir.get(&(name, arity)).cloned() {
|
||||
self.try_call(name, arity, idx.get())
|
||||
} else {
|
||||
Err(self.machine_st.throw_undefined_error(name, arity))
|
||||
self.undefined_procedure(name, arity)
|
||||
}
|
||||
} else {
|
||||
let stub = functor_stub(name, arity);
|
||||
@@ -783,14 +1139,14 @@ impl Machine {
|
||||
if let Some(idx) = self.indices.code_dir.get(&(name, arity)).cloned() {
|
||||
self.try_execute(name, arity, idx.get())
|
||||
} else {
|
||||
Err(self.machine_st.throw_undefined_error(name, arity))
|
||||
self.undefined_procedure(name, arity)
|
||||
}
|
||||
} else {
|
||||
if let Some(module) = self.indices.modules.get(&module_name) {
|
||||
if let Some(idx) = module.code_dir.get(&(name, arity)).cloned() {
|
||||
self.try_execute(name, arity, idx.get())
|
||||
} else {
|
||||
Err(self.machine_st.throw_undefined_error(name, arity))
|
||||
self.undefined_procedure(name, arity)
|
||||
}
|
||||
} else {
|
||||
let stub = functor_stub(name, arity);
|
||||
@@ -841,7 +1197,7 @@ impl Machine {
|
||||
|
||||
if let Some(&(_, b_cutoff, prev_block)) = self.machine_st.cont_pts.last() {
|
||||
if self.machine_st.b < b_cutoff {
|
||||
let (idx, arity) = if self.machine_st.block > prev_block {
|
||||
let (idx, arity) = if self.machine_st.effective_block() > prev_block {
|
||||
(r_c_w_h, 0)
|
||||
} else {
|
||||
self.machine_st.registers[1] = fixnum_as_cell!(
|
||||
@@ -876,14 +1232,22 @@ impl Machine {
|
||||
TrailEntryTag::TrailedAttrVar => {
|
||||
self.machine_st.heap[h] = attr_var_as_cell!(h);
|
||||
}
|
||||
TrailEntryTag::TrailedAttrVarHeapLink => {
|
||||
self.machine_st.heap[h] = heap_loc_as_cell!(h);
|
||||
}
|
||||
TrailEntryTag::TrailedAttrVarListLink => {
|
||||
let l = self.machine_st.trail[i + 1].get_value() as usize;
|
||||
|
||||
if l < self.machine_st.hb {
|
||||
self.machine_st.heap[h] = list_loc_as_cell!(l);
|
||||
if h == l {
|
||||
self.machine_st.heap[h] = heap_loc_as_cell!(h);
|
||||
} else {
|
||||
read_heap_cell!(self.machine_st.heap[l],
|
||||
(HeapCellValueTag::Var) => {
|
||||
self.machine_st.heap[h] = list_loc_as_cell!(l);
|
||||
}
|
||||
_ => {
|
||||
self.machine_st.heap[h] = heap_loc_as_cell!(l);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.machine_st.heap[h] = heap_loc_as_cell!(h);
|
||||
}
|
||||
@@ -910,4 +1274,4 @@ impl Machine {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::atom_table::*;
|
||||
use ordered_float::OrderedFloat;
|
||||
use rug::*;
|
||||
use dashu::*;
|
||||
use std::collections::BTreeMap;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -181,7 +181,7 @@ impl<'a> HeapPStrIter<'a> {
|
||||
self.brent_st.hare = result.focus;
|
||||
} else {
|
||||
read_heap_cell!(self.heap[result.focus],
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::Str) => {
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::Str | HeapCellValueTag::PStr) => {
|
||||
self.focus = self.heap[self.brent_st.hare];
|
||||
}
|
||||
_ => {
|
||||
|
||||
+53
-442
@@ -2,7 +2,7 @@ use crate::atom_table::*;
|
||||
use crate::codegen::CodeGenSettings;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
use crate::machine::disjuncts::*;
|
||||
use crate::machine::loader::*;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::parser::ast::*;
|
||||
@@ -10,35 +10,7 @@ use crate::parser::ast::*;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::TryFrom;
|
||||
use std::rc::Rc;
|
||||
|
||||
/*
|
||||
* The preprocessor fabricates if-then-else ( .. -> ... ; ...)
|
||||
* clauses into nameless standalone predicates, which it queues for
|
||||
* later preprocessing and compilation. Fabricated predicates inherit
|
||||
* explicit "cut variables" from the handwritten predicate
|
||||
* surrounding their source if-then-else. They must be specially
|
||||
* handled.
|
||||
*/
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum CutContext {
|
||||
BlocksCuts,
|
||||
HasCutVariable,
|
||||
}
|
||||
|
||||
pub(crate) fn fold_by_str<I>(terms: I, mut term: Term, sym: Atom) -> Term
|
||||
where
|
||||
I: DoubleEndedIterator<Item = Term>,
|
||||
{
|
||||
for prec in terms.rev() {
|
||||
term = Term::Clause(Cell::default(), sym, vec![prec, term]);
|
||||
}
|
||||
|
||||
term
|
||||
}
|
||||
|
||||
pub(crate) fn to_op_decl(
|
||||
prec: u16,
|
||||
@@ -132,6 +104,13 @@ fn setup_module_export(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn build_rule_body(vars: &[Term], body_term: Term) -> Term {
|
||||
let head_term = Term::Clause(Cell::default(), atom!(""), vars.iter().cloned().collect());
|
||||
let rule = vec![head_term, body_term];
|
||||
|
||||
Term::Clause(Cell::default(), atom!(":-"), rule)
|
||||
}
|
||||
|
||||
pub(super) fn setup_module_export_list(
|
||||
mut export_list: Term,
|
||||
atom_tbl: &mut AtomTable,
|
||||
@@ -325,110 +304,6 @@ fn setup_meta_predicate<'a, LS: LoadState<'a>>(
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_clauses(tls: &mut VecDeque<TopLevel>) -> Result<TopLevel, CompilationError> {
|
||||
let mut clauses = vec![];
|
||||
|
||||
while let Some(tl) = tls.pop_front() {
|
||||
match tl {
|
||||
TopLevel::Query(_) if clauses.is_empty() && tls.is_empty() => {
|
||||
return Ok(tl);
|
||||
}
|
||||
TopLevel::Query(_) => {
|
||||
return Err(CompilationError::InconsistentEntry);
|
||||
}
|
||||
TopLevel::Fact(fact) => {
|
||||
let clause = PredicateClause::Fact(fact);
|
||||
clauses.push(clause);
|
||||
}
|
||||
TopLevel::Rule(rule) => {
|
||||
let clause = PredicateClause::Rule(rule);
|
||||
clauses.push(clause);
|
||||
}
|
||||
TopLevel::Predicate(predicate) => clauses.extend(predicate.into_iter()),
|
||||
}
|
||||
}
|
||||
|
||||
if clauses.is_empty() {
|
||||
Err(CompilationError::InconsistentEntry)
|
||||
} else {
|
||||
Ok(TopLevel::Predicate(clauses))
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_cut_variables_as(terms: &mut Vec<Term>, name: Atom) {
|
||||
for term in terms.iter_mut() {
|
||||
match term {
|
||||
&mut Term::Literal(_, Literal::Atom(ref mut var)) if *var == atom!("!") => {
|
||||
*var = name;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_cut_variable(term: &mut Term) -> bool {
|
||||
let cut_var_found = match term {
|
||||
&mut Term::Literal(_, Literal::Atom(ref var)) if *var == atom!("!") => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if cut_var_found {
|
||||
*term = Term::Var(Cell::default(), Rc::new(String::from("!")));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_cut_variables(terms: &mut Vec<Term>) -> bool {
|
||||
let mut found_cut_var = false;
|
||||
|
||||
for item in terms.iter_mut() {
|
||||
found_cut_var = mark_cut_variable(item) || found_cut_var;
|
||||
}
|
||||
|
||||
found_cut_var
|
||||
}
|
||||
|
||||
// terms is a list of goals composing one clause in a (;) functor. it
|
||||
// checks that the first (and only) of these clauses is a ->. if so,
|
||||
// it expands its terms using a blocked_!.
|
||||
fn check_for_internal_if_then(terms: &mut Vec<Term>) {
|
||||
if terms.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(Term::Clause(_, name, ref subterms)) = terms.last() {
|
||||
if *name != atom!("->") || subterms.len() != 2 {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(Term::Clause(_, _, mut subterms)) = terms.pop() {
|
||||
let mut conq_terms = VecDeque::from(unfold_by_str(subterms.pop().unwrap(), atom!(",")));
|
||||
let mut pre_cut_terms = VecDeque::from(unfold_by_str(subterms.pop().unwrap(), atom!(",")));
|
||||
|
||||
conq_terms.push_front(Term::Literal(
|
||||
Cell::default(),
|
||||
Literal::Atom(atom!("blocked_!")),
|
||||
));
|
||||
|
||||
while let Some(term) = pre_cut_terms.pop_back() {
|
||||
conq_terms.push_front(term);
|
||||
}
|
||||
|
||||
let tail_term = conq_terms.pop_back().unwrap();
|
||||
|
||||
terms.push(fold_by_str(
|
||||
conq_terms.into_iter(),
|
||||
tail_term,
|
||||
atom!(","),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn setup_declaration<'a, LS: LoadState<'a>>(
|
||||
loader: &mut Loader<'a, LS>,
|
||||
mut terms: Vec<Term>,
|
||||
@@ -570,7 +445,7 @@ fn build_meta_predicate_clause<'a, LS: LoadState<'a>>(
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clause_to_query_term<'a, LS: LoadState<'a>>(
|
||||
pub(super) fn clause_to_query_term<'a, LS: LoadState<'a>>(
|
||||
loader: &mut Loader<'a, LS>,
|
||||
name: Atom,
|
||||
mut terms: Vec<Term>,
|
||||
@@ -609,7 +484,7 @@ fn clause_to_query_term<'a, LS: LoadState<'a>>(
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
|
||||
pub(super) fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
|
||||
loader: &mut Loader<'a, LS>,
|
||||
module_name: Atom,
|
||||
name: Atom,
|
||||
@@ -647,308 +522,58 @@ fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
|
||||
QueryTerm::Clause(Cell::default(), ct, terms, call_policy)
|
||||
}
|
||||
|
||||
fn compute_head(term: &Term) -> Vec<Term> {
|
||||
let mut vars = IndexSet::new();
|
||||
|
||||
for term in post_order_iter(term) {
|
||||
if let TermRef::Var(_, _, v) = term {
|
||||
vars.insert(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
vars.insert(Rc::new(String::from("!")));
|
||||
vars.into_iter()
|
||||
.map(|v| Term::Var(Cell::default(), v))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn build_rule_body(vars: &[Term], body_term: Term) -> Term {
|
||||
let head_term = Term::Clause(Cell::default(), atom!(""), vars.iter().cloned().collect());
|
||||
let rule = vec![head_term, body_term];
|
||||
|
||||
Term::Clause(Cell::default(), atom!(":-"), rule)
|
||||
}
|
||||
|
||||
// the terms form the body of the rule. We create a head, by
|
||||
// gathering variables from the body of terms and recording them
|
||||
// in the head clause.
|
||||
fn build_rule(body_term: Term) -> (JumpStub, VecDeque<Term>) {
|
||||
// collect the vars of body_term into a head, return the num_vars
|
||||
// (the arity) as well.
|
||||
let vars = compute_head(&body_term);
|
||||
let rule = build_rule_body(&vars, body_term);
|
||||
|
||||
(vars, VecDeque::from(vec![rule]))
|
||||
}
|
||||
|
||||
fn build_disjunct(body_term: Term) -> (JumpStub, VecDeque<Term>) {
|
||||
let vars = compute_head(&body_term);
|
||||
let results = unfold_by_str(body_term, atom!(";"))
|
||||
.into_iter()
|
||||
.map(|term| {
|
||||
let mut subterms = unfold_by_str(term, atom!(","));
|
||||
mark_cut_variables(&mut subterms);
|
||||
|
||||
check_for_internal_if_then(&mut subterms);
|
||||
|
||||
let term = subterms.pop().unwrap();
|
||||
let clause = fold_by_str(subterms.into_iter(), term, atom!(","));
|
||||
|
||||
build_rule_body(&vars, clause)
|
||||
})
|
||||
.collect();
|
||||
|
||||
(vars, results)
|
||||
}
|
||||
|
||||
fn build_if_then(prec: Term, conq: Term) -> (JumpStub, VecDeque<Term>) {
|
||||
let mut prec_seq = unfold_by_str(prec, atom!(","));
|
||||
let comma_sym = atom!(",");
|
||||
let cut_sym = Literal::Atom(atom!("!"));
|
||||
|
||||
prec_seq.push(Term::Literal(Cell::default(), cut_sym));
|
||||
|
||||
mark_cut_variables_as(&mut prec_seq, atom!("blocked_!"));
|
||||
|
||||
let mut conq_seq = unfold_by_str(conq, atom!(","));
|
||||
|
||||
mark_cut_variables(&mut conq_seq);
|
||||
prec_seq.extend(conq_seq.into_iter());
|
||||
|
||||
let back_term = prec_seq.pop().unwrap();
|
||||
let front_term = prec_seq.pop().unwrap();
|
||||
|
||||
let body_term = Term::Clause(
|
||||
Cell::default(),
|
||||
comma_sym,
|
||||
vec![front_term, back_term],
|
||||
);
|
||||
|
||||
build_rule(fold_by_str(prec_seq.into_iter(), body_term, comma_sym))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Preprocessor {
|
||||
queue: VecDeque<VecDeque<Term>>,
|
||||
settings: CodeGenSettings,
|
||||
}
|
||||
|
||||
impl Preprocessor {
|
||||
pub(super) fn new(settings: CodeGenSettings) -> Self {
|
||||
Preprocessor {
|
||||
queue: VecDeque::new(),
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_fact(&mut self, term: Term) -> Result<Term, CompilationError> {
|
||||
fn setup_fact(&mut self, term: Term) -> Result<(Fact, VarData), CompilationError> {
|
||||
match term {
|
||||
Term::Clause(..) | Term::Literal(_, Literal::Atom(..)) => Ok(term),
|
||||
Term::Clause(..) | Term::Literal(_, Literal::Atom(..)) => {
|
||||
let classifier = VariableClassifier::new(
|
||||
self.settings.default_call_policy(),
|
||||
);
|
||||
|
||||
let (head, var_data) = classifier.classify_fact(term)?;
|
||||
Ok((Fact { head }, var_data))
|
||||
}
|
||||
_ => Err(CompilationError::InadmissibleFact),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_query_term<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
term: Term,
|
||||
) -> Result<QueryTerm, CompilationError> {
|
||||
match term {
|
||||
Term::Literal(_, Literal::Atom(name)) => {
|
||||
if name == atom!("!") || name == atom!("blocked_!") {
|
||||
Ok(QueryTerm::BlockedCut)
|
||||
} else {
|
||||
Ok(clause_to_query_term(
|
||||
loader,
|
||||
name,
|
||||
vec![],
|
||||
self.settings.default_call_policy(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Term::Literal(_, Literal::Char('!')) => Ok(QueryTerm::BlockedCut),
|
||||
Term::Var(_, ref v) if v.as_str() == "!" => {
|
||||
Ok(QueryTerm::UnblockedCut(Cell::default()))
|
||||
}
|
||||
Term::Clause(r, name, mut terms) => match (name, terms.len()) {
|
||||
(atom!(";"), 2) => {
|
||||
let term = Term::Clause(r, name, terms);
|
||||
|
||||
let (stub, clauses) = build_disjunct(term);
|
||||
self.queue.push_back(clauses);
|
||||
|
||||
Ok(QueryTerm::Jump(stub))
|
||||
}
|
||||
(atom!("->"), 2) => {
|
||||
let conq = terms.pop().unwrap();
|
||||
let prec = terms.pop().unwrap();
|
||||
|
||||
let (stub, clauses) = build_if_then(prec, conq);
|
||||
self.queue.push_back(clauses);
|
||||
|
||||
Ok(QueryTerm::Jump(stub))
|
||||
}
|
||||
(atom!("\\+"), 1) => {
|
||||
terms.push(Term::Literal(
|
||||
Cell::default(),
|
||||
Literal::Atom(atom!("$fail")),
|
||||
));
|
||||
|
||||
let conq = Term::Literal(Cell::default(), Literal::Atom(atom!("true")));
|
||||
|
||||
let prec = Term::Clause(Cell::default(), atom!("->"), terms);
|
||||
let terms = vec![prec, conq];
|
||||
|
||||
let term = Term::Clause(Cell::default(), atom!(";"), terms);
|
||||
let (stub, clauses) = build_disjunct(term);
|
||||
|
||||
debug_assert!(clauses.len() > 0);
|
||||
self.queue.push_back(clauses);
|
||||
|
||||
Ok(QueryTerm::Jump(stub))
|
||||
}
|
||||
(atom!("$get_level"), 1) => {
|
||||
if let Term::Var(_, ref var) = &terms[0] {
|
||||
Ok(QueryTerm::GetLevelAndUnify(Cell::default(), var.clone()))
|
||||
} else {
|
||||
Err(CompilationError::InadmissibleQueryTerm)
|
||||
}
|
||||
}
|
||||
(atom!(":"), 2) => {
|
||||
let predicate_name = terms.pop().unwrap();
|
||||
let module_name = terms.pop().unwrap();
|
||||
|
||||
match (module_name, predicate_name) {
|
||||
(
|
||||
Term::Literal(_, Literal::Atom(module_name)),
|
||||
Term::Literal(_, Literal::Atom(predicate_name)),
|
||||
) => Ok(qualified_clause_to_query_term(
|
||||
loader,
|
||||
module_name,
|
||||
predicate_name,
|
||||
vec![],
|
||||
self.settings.default_call_policy(),
|
||||
)),
|
||||
(
|
||||
Term::Literal(_, Literal::Atom(module_name)),
|
||||
Term::Clause(_, name, terms),
|
||||
) => Ok(qualified_clause_to_query_term(
|
||||
loader,
|
||||
module_name,
|
||||
name,
|
||||
terms,
|
||||
self.settings.default_call_policy()
|
||||
)),
|
||||
(module_name, predicate_name) => {
|
||||
terms.push(module_name);
|
||||
terms.push(predicate_name);
|
||||
|
||||
Ok(clause_to_query_term(
|
||||
loader,
|
||||
atom!("call"),
|
||||
vec![Term::Clause(r, name, terms)],
|
||||
self.settings.default_call_policy(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Ok(clause_to_query_term(loader, name, terms,
|
||||
self.settings.default_call_policy())),
|
||||
},
|
||||
Term::Var(..) => Ok(QueryTerm::Clause(
|
||||
Cell::default(),
|
||||
ClauseType::CallN(1),
|
||||
vec![term],
|
||||
self.settings.default_call_policy(),
|
||||
)),
|
||||
_ => Err(CompilationError::InadmissibleQueryTerm),
|
||||
}
|
||||
}
|
||||
|
||||
fn pre_query_term<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
term: Term,
|
||||
) -> Result<QueryTerm, CompilationError> {
|
||||
match term {
|
||||
Term::Clause(r, name, mut subterms) => {
|
||||
if subterms.len() == 1 && name == atom!("$call_with_inference_counting") {
|
||||
self.to_query_term(loader, subterms.pop().unwrap())
|
||||
.map(|mut query_term| {
|
||||
query_term.set_call_policy(CallPolicy::Counted);
|
||||
query_term
|
||||
})
|
||||
} else {
|
||||
let clause = Term::Clause(r, name, subterms);
|
||||
self.to_query_term(loader, clause)
|
||||
}
|
||||
}
|
||||
_ => self.to_query_term(loader, term),
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_query<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
terms: Vec<Term>,
|
||||
cut_context: CutContext,
|
||||
) -> Result<Vec<QueryTerm>, CompilationError> {
|
||||
let mut query_terms = vec![];
|
||||
let mut work_queue = VecDeque::from(terms);
|
||||
|
||||
while let Some(term) = work_queue.pop_front() {
|
||||
let mut term = term;
|
||||
|
||||
if let Term::Clause(cell, name, terms) = term {
|
||||
if name == atom!(",") && terms.len() == 2 {
|
||||
let term = Term::Clause(cell, name, terms);
|
||||
let mut subterms = unfold_by_str(term, atom!(","));
|
||||
|
||||
while let Some(subterm) = subterms.pop() {
|
||||
work_queue.push_front(subterm);
|
||||
}
|
||||
|
||||
continue;
|
||||
} else {
|
||||
term = Term::Clause(cell, name, terms);
|
||||
}
|
||||
}
|
||||
|
||||
if let CutContext::HasCutVariable = cut_context {
|
||||
mark_cut_variable(&mut term);
|
||||
}
|
||||
|
||||
query_terms.push(self.pre_query_term(loader, term)?);
|
||||
}
|
||||
|
||||
Ok(query_terms)
|
||||
}
|
||||
|
||||
fn setup_rule<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
mut terms: Vec<Term>,
|
||||
cut_context: CutContext,
|
||||
) -> Result<Rule, CompilationError> {
|
||||
let post_head_terms: Vec<_> = terms.drain(1..).collect();
|
||||
let mut query_terms = self.setup_query(loader, post_head_terms, cut_context)?;
|
||||
head: Term,
|
||||
body: Term,
|
||||
) -> Result<(Rule, VarData), CompilationError> {
|
||||
let classifier = VariableClassifier::new(
|
||||
self.settings.default_call_policy(),
|
||||
);
|
||||
|
||||
let clauses = query_terms.drain(1..).collect();
|
||||
let qt = query_terms.pop().unwrap();
|
||||
let (head, clauses, var_data) = classifier.classify_rule(loader, head, body)?;
|
||||
|
||||
match terms.pop().unwrap() {
|
||||
Term::Clause(_, name, terms) => Ok(Rule {
|
||||
head: (name, terms, qt),
|
||||
match head {
|
||||
Term::Clause(_, name, terms) => Ok((Rule {
|
||||
head: (name, terms),
|
||||
clauses,
|
||||
}),
|
||||
Term::Literal(_, Literal::Atom(name)) => Ok(Rule {
|
||||
head: (name, vec![], qt),
|
||||
}, var_data)),
|
||||
Term::Literal(_, Literal::Atom(name)) => Ok((Rule {
|
||||
head: (name, vec![]),
|
||||
clauses,
|
||||
}),
|
||||
}, var_data)),
|
||||
_ => Err(CompilationError::InvalidRuleHead),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn try_term_to_query<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
@@ -961,63 +586,49 @@ impl Preprocessor {
|
||||
cut_context,
|
||||
)?))
|
||||
}
|
||||
*/
|
||||
|
||||
pub(super) fn try_term_to_tl<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
term: Term,
|
||||
cut_context: CutContext,
|
||||
) -> Result<TopLevel, CompilationError> {
|
||||
match term {
|
||||
Term::Clause(r, name, terms) => {
|
||||
if name == atom!("?-") {
|
||||
self.try_term_to_query(loader, terms, cut_context)
|
||||
} else if name == atom!(":-") && terms.len() == 2 {
|
||||
Ok(TopLevel::Rule(self.setup_rule(
|
||||
loader,
|
||||
terms,
|
||||
cut_context,
|
||||
)?))
|
||||
Term::Clause(r, name, mut terms) => {
|
||||
let is_rule = name == atom!(":-") && terms.len() == 2;
|
||||
|
||||
if is_rule {
|
||||
let tail = terms.pop().unwrap();
|
||||
let head = terms.pop().unwrap();
|
||||
|
||||
let (rule, var_data) = self.setup_rule(loader, head, tail)?;
|
||||
Ok(TopLevel::Rule(rule, var_data))
|
||||
} else {
|
||||
let term = Term::Clause(r, name, terms);
|
||||
Ok(TopLevel::Fact(self.setup_fact(term)?))
|
||||
let (fact, var_data) = self.setup_fact(term)?;
|
||||
Ok(TopLevel::Fact(fact, var_data))
|
||||
}
|
||||
}
|
||||
term => Ok(TopLevel::Fact(self.setup_fact(term)?)),
|
||||
term => {
|
||||
let (fact, var_data) = self.setup_fact(term)?;
|
||||
Ok(TopLevel::Fact(fact, var_data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn try_terms_to_tls<'a, I: IntoIterator<Item = Term>, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
terms: I,
|
||||
cut_context: CutContext,
|
||||
) -> Result<VecDeque<TopLevel>, CompilationError> {
|
||||
let mut results = VecDeque::new();
|
||||
|
||||
for term in terms.into_iter() {
|
||||
results.push_back(self.try_term_to_tl(loader, term, cut_context)?);
|
||||
results.push_back(self.try_term_to_tl(loader, term)?);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub(super) fn parse_queue<'a, LS: LoadState<'a>>(
|
||||
&mut self,
|
||||
loader: &mut Loader<'a, LS>,
|
||||
) -> Result<VecDeque<TopLevel>, CompilationError> {
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
while let Some(terms) = self.queue.pop_front() {
|
||||
let clauses = merge_clauses(&mut self.try_terms_to_tls(
|
||||
loader,
|
||||
terms,
|
||||
CutContext::HasCutVariable,
|
||||
)?)?;
|
||||
|
||||
queue.push_back(clauses);
|
||||
}
|
||||
|
||||
Ok(queue)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
:- module('$project_atts', [copy_term/3]).
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(error), [can_be/2]).
|
||||
:- use_module(library(lambda)).
|
||||
:- use_module(library(lists), [foldl/4, maplist/2]).
|
||||
|
||||
project_attributes(QueryVars, AttrVars) :-
|
||||
gather_attr_modules(AttrVars, Modules0),
|
||||
phrase(gather_attr_modules(AttrVars), Modules0),
|
||||
sort(Modules0, Modules),
|
||||
call_project_attributes(Modules, QueryVars, AttrVars).
|
||||
|
||||
@@ -17,19 +22,14 @@ project_attributes(QueryVars, AttrVars) :-
|
||||
call_project_attributes([], _, _).
|
||||
call_project_attributes([Module|Modules], QueryVars, AttrVars) :-
|
||||
( catch(Module:project_attributes(QueryVars, AttrVars),
|
||||
E,
|
||||
'$project_atts':'$print_project_attributes_exception'(Module, E)
|
||||
)
|
||||
E,
|
||||
'$project_atts':'$print_project_attributes_exception'(Module, E)
|
||||
)
|
||||
-> true
|
||||
; true
|
||||
),
|
||||
call_project_attributes(Modules, QueryVars, AttrVars).
|
||||
|
||||
call_attribute_goals([], _, _).
|
||||
call_attribute_goals([Module|Modules], GoalCaller, AttrVars) :-
|
||||
call(GoalCaller, AttrVars, Module, Goals),
|
||||
call_attribute_goals(Modules, GoalCaller, AttrVars).
|
||||
|
||||
'$print_attribute_goals_exception'(Module, E) :-
|
||||
( E = error(evaluation_error((Module:attribute_goals)/3), attribute_goals/3)
|
||||
; E = error(existence_error(procedure, attribute_goals/3), attribute_goals/3)
|
||||
@@ -38,20 +38,6 @@ call_attribute_goals([Module|Modules], GoalCaller, AttrVars) :-
|
||||
nl
|
||||
).
|
||||
|
||||
call_query_var_goals([], _, []).
|
||||
call_query_var_goals([AttrVar|AttrVars], Module, Goals) :-
|
||||
( catch(( Module:attribute_goals(AttrVar, Goals, RGoals0),
|
||||
atts:'$default_attr_list'(Module, AttrVar, RGoals0, RGoals)
|
||||
),
|
||||
E,
|
||||
( '$project_atts':'$print_attribute_goals_exception'(Module, E),
|
||||
atts:'$default_attr_list'(Module, AttrVar, Goals, RGoals)
|
||||
))
|
||||
-> true
|
||||
; atts:'$default_attr_list'(Module, AttrVar, Goals, RGoals)
|
||||
),
|
||||
call_query_var_goals(AttrVars, Module, RGoals).
|
||||
|
||||
call_attr_var_goals([], _, []).
|
||||
call_attr_var_goals([AttrVar|AttrVars], Module, Goals) :-
|
||||
( catch(Module:attribute_goals(AttrVar, Goals, RGoals),
|
||||
@@ -77,25 +63,52 @@ call_attribute_goals_with_module_prefix([Module | Modules], GoalCaller, AttrVars
|
||||
module_prefixed_goals(Goals0, Module, Goals, Gs),
|
||||
call_attribute_goals_with_module_prefix(Modules, GoalCaller, AttrVars, Gs).
|
||||
|
||||
gather_attr_modules([]) --> [].
|
||||
gather_attr_modules([AttrVar|AttrVars]) -->
|
||||
{ '$get_attr_list'(AttrVar, Attrs) },
|
||||
copy_attribute_modules(Attrs),
|
||||
gather_attr_modules(AttrVars).
|
||||
|
||||
gather_attr_modules([], []).
|
||||
gather_attr_modules([AttrVar|AttrVars], Modules) :-
|
||||
'$get_attr_list'(AttrVar, Attrs),
|
||||
copy_attribute_modules(Attrs, Modules, Modules0),
|
||||
gather_attr_modules(AttrVars, Modules0).
|
||||
copy_attribute_modules(Attrs) -->
|
||||
{ var(Attrs) },
|
||||
!.
|
||||
copy_attribute_modules([Module:_|Attrs]) -->
|
||||
[Module],
|
||||
copy_attribute_modules(Attrs).
|
||||
|
||||
copy_attribute_modules(Attrs, Ls, Ls) :-
|
||||
var(Attrs), !.
|
||||
copy_attribute_modules([Module:_|Attrs], [Module|Modules0], Modules1) :-
|
||||
copy_attribute_modules(Attrs, Modules0, Modules1).
|
||||
gather_residual_goals_(M, V, V0, V1) :-
|
||||
( catch(M:attribute_goals(V, V0, V1),
|
||||
E,
|
||||
('$project_atts':'$print_attribute_goals_exception'(M, E),
|
||||
V0 = V1)
|
||||
) ->
|
||||
true
|
||||
; V0 = V1
|
||||
).
|
||||
|
||||
gather_residual_goals(M, V) -->
|
||||
gather_residual_goals_(M, V),
|
||||
atts:'$default_attr_list'(M, V).
|
||||
|
||||
copy_term(Source, Dest, Goals) :-
|
||||
'$term_attributed_variables'(Source, AttrVars),
|
||||
gather_attr_modules(AttrVars, Modules0),
|
||||
sort(Modules0, Modules),
|
||||
call_attribute_goals_with_module_prefix(Modules, '$project_atts':call_query_var_goals,
|
||||
AttrVars, Goals0),
|
||||
sort(Goals0, Goals1),
|
||||
!,
|
||||
'$copy_term_without_attr_vars'([Source | Goals1], [Dest | Goals]).
|
||||
gather_residual_goals([]) --> [].
|
||||
gather_residual_goals([V|Vs]) -->
|
||||
{ '$get_attr_list'(V, Attrs),
|
||||
phrase(copy_attribute_modules(Attrs), Modules0),
|
||||
sort(Modules0, Modules) },
|
||||
foldl(V+\M^gather_residual_goals(M, V), Modules),
|
||||
gather_residual_goals(Vs).
|
||||
|
||||
delete_all_attributes_from_var(V) :- '$delete_all_attributes_from_var'(V).
|
||||
|
||||
copy_term(Term, Copy, Gs) :-
|
||||
can_be(list, Gs),
|
||||
findall(Term-Rs, term_residual_goals(Term,Rs), [Copy-Gs]),
|
||||
( var(Gs) ->
|
||||
Gs = []
|
||||
; true
|
||||
).
|
||||
|
||||
term_residual_goals(Term,Rs) :-
|
||||
'$term_attributed_variables'(Term, Vs),
|
||||
phrase(gather_residual_goals(Vs), Rs),
|
||||
maplist(delete_all_attributes_from_var, Vs).
|
||||
|
||||
+8
-12
@@ -36,14 +36,9 @@ impl Drop for Stack {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct FramePrelude {
|
||||
pub(crate) num_cells: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AndFramePrelude {
|
||||
pub(crate) univ_prelude: FramePrelude,
|
||||
pub(crate) num_cells: usize,
|
||||
pub(crate) e: usize,
|
||||
pub(crate) cp: usize,
|
||||
}
|
||||
@@ -113,7 +108,7 @@ impl IndexMut<usize> for Stack {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OrFramePrelude {
|
||||
pub(crate) univ_prelude: FramePrelude,
|
||||
pub(crate) num_cells: usize,
|
||||
pub(crate) e: usize,
|
||||
pub(crate) cp: usize,
|
||||
pub(crate) b: usize,
|
||||
@@ -123,6 +118,7 @@ pub(crate) struct OrFramePrelude {
|
||||
pub(crate) tr: usize,
|
||||
pub(crate) h: usize,
|
||||
pub(crate) b0: usize,
|
||||
pub(crate) attr_var_queue_len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -206,8 +202,8 @@ impl Stack {
|
||||
offset += mem::size_of::<HeapCellValue>();
|
||||
}
|
||||
|
||||
let and_frame = &mut *(new_ptr as *mut AndFrame);
|
||||
and_frame.prelude.univ_prelude.num_cells = num_cells;
|
||||
let and_frame = self.index_and_frame_mut(e);
|
||||
and_frame.prelude.num_cells = num_cells;
|
||||
|
||||
e
|
||||
}
|
||||
@@ -230,8 +226,8 @@ impl Stack {
|
||||
offset += mem::size_of::<HeapCellValue>();
|
||||
}
|
||||
|
||||
let or_frame = &mut *(new_ptr as *mut OrFrame);
|
||||
or_frame.prelude.univ_prelude.num_cells = num_cells;
|
||||
let or_frame = self.index_or_frame_mut(b);
|
||||
or_frame.prelude.num_cells = num_cells;
|
||||
|
||||
b
|
||||
}
|
||||
@@ -297,7 +293,7 @@ mod tests {
|
||||
0// 10 * mem::size_of::<HeapCellValue>() + prelude_size::<AndFrame>()
|
||||
);
|
||||
|
||||
assert_eq!(and_frame.prelude.univ_prelude.num_cells, 10);
|
||||
assert_eq!(and_frame.prelude.num_cells, 10);
|
||||
|
||||
for idx in 0..10 {
|
||||
assert_eq!(and_frame[idx + 1], stack_loc_as_cell!(AndFrame, e, idx + 1));
|
||||
|
||||
+288
-225
@@ -9,6 +9,7 @@ use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::types::*;
|
||||
use crate::http::HttpResponse;
|
||||
|
||||
pub use modular_bitfield::prelude::*;
|
||||
|
||||
@@ -26,7 +27,6 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
|
||||
use native_tls::TlsStream;
|
||||
use hyper::body::{Bytes, Sender};
|
||||
|
||||
#[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[bits = 1]
|
||||
@@ -102,6 +102,13 @@ impl EOFAction {
|
||||
#[derive(Debug)]
|
||||
pub struct ByteStream(Cursor<Vec<u8>>);
|
||||
|
||||
impl ByteStream {
|
||||
#[inline(always)]
|
||||
pub fn from_string(string: String) -> Self {
|
||||
ByteStream(Cursor::new(string.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for ByteStream {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
@@ -269,28 +276,42 @@ impl Read for HttpReadStream {
|
||||
}
|
||||
|
||||
pub struct HttpWriteStream {
|
||||
body_writer: Sender,
|
||||
status_code: u16,
|
||||
headers: hyper::HeaderMap,
|
||||
response: TypedArenaPtr<HttpResponse>,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Debug for HttpWriteStream {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Http Write Stream")
|
||||
write!(f, "Http Write Stream")
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for HttpWriteStream {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let bytes = Bytes::copy_from_slice(buf);
|
||||
let len = bytes.len();
|
||||
match self.body_writer.try_send_data(bytes) {
|
||||
Ok(()) => Ok(len),
|
||||
Err(_) => Err(std::io::Error::from(ErrorKind::Interrupted))
|
||||
}
|
||||
self.buffer.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
let (ready, response, cvar) = &**self.response;
|
||||
|
||||
let mut ready = ready.lock().unwrap();
|
||||
{
|
||||
let mut response = response.lock().unwrap();
|
||||
|
||||
let bytes = bytes::Bytes::copy_from_slice(&self.buffer);
|
||||
let mut response_ = hyper::Response::builder()
|
||||
.status(self.status_code);
|
||||
*response_.headers_mut().unwrap() = self.headers.clone();
|
||||
*response = Some(response_.body(http_body_util::Full::new(bytes)).unwrap());
|
||||
}
|
||||
*ready = true;
|
||||
cvar.notify_one();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -505,7 +526,7 @@ impl Stream {
|
||||
ArenaHeaderTag::NamedTcpStream => Stream::NamedTcp(TypedArenaPtr::new(ptr as *mut _)),
|
||||
ArenaHeaderTag::NamedTlsStream => Stream::NamedTls(TypedArenaPtr::new(ptr as *mut _)),
|
||||
ArenaHeaderTag::HttpReadStream => Stream::HttpRead(TypedArenaPtr::new(ptr as *mut _)),
|
||||
ArenaHeaderTag::HttpWriteStream => Stream::HttpWrite(TypedArenaPtr::new(ptr as *mut _)),
|
||||
ArenaHeaderTag::HttpWriteStream => Stream::HttpWrite(TypedArenaPtr::new(ptr as *mut _)),
|
||||
ArenaHeaderTag::ReadlineStream => Stream::Readline(TypedArenaPtr::new(ptr as *mut _)),
|
||||
ArenaHeaderTag::StaticStringStream => {
|
||||
Stream::StaticString(TypedArenaPtr::new(ptr as *mut _))
|
||||
@@ -559,7 +580,7 @@ impl Stream {
|
||||
Stream::NamedTcp(ptr) => ptr.header_ptr(),
|
||||
Stream::NamedTls(ptr) => ptr.header_ptr(),
|
||||
Stream::HttpRead(ptr) => ptr.header_ptr(),
|
||||
Stream::HttpWrite(ptr) => ptr.header_ptr(),
|
||||
Stream::HttpWrite(ptr) => ptr.header_ptr(),
|
||||
Stream::Null(_) => ptr::null(),
|
||||
Stream::Readline(ptr) => ptr.header_ptr(),
|
||||
Stream::StandardOutput(ptr) => ptr.header_ptr(),
|
||||
@@ -576,7 +597,7 @@ impl Stream {
|
||||
Stream::NamedTcp(ref ptr) => &ptr.options,
|
||||
Stream::NamedTls(ref ptr) => &ptr.options,
|
||||
Stream::HttpRead(ref ptr) => &ptr.options,
|
||||
Stream::HttpWrite(ref ptr) => &ptr.options,
|
||||
Stream::HttpWrite(ref ptr) => &ptr.options,
|
||||
Stream::Null(ref options) => options,
|
||||
Stream::Readline(ref ptr) => &ptr.options,
|
||||
Stream::StandardOutput(ref ptr) => &ptr.options,
|
||||
@@ -593,7 +614,7 @@ impl Stream {
|
||||
Stream::NamedTcp(ref mut ptr) => &mut ptr.options,
|
||||
Stream::NamedTls(ref mut ptr) => &mut ptr.options,
|
||||
Stream::HttpRead(ref mut ptr) => &mut ptr.options,
|
||||
Stream::HttpWrite(ref mut ptr) => &mut ptr.options,
|
||||
Stream::HttpWrite(ref mut ptr) => &mut ptr.options,
|
||||
Stream::Null(ref mut options) => options,
|
||||
Stream::Readline(ref mut ptr) => &mut ptr.options,
|
||||
Stream::StandardOutput(ref mut ptr) => &mut ptr.options,
|
||||
@@ -611,7 +632,7 @@ impl Stream {
|
||||
Stream::NamedTcp(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::NamedTls(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::HttpRead(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::HttpWrite(_) => {}
|
||||
Stream::HttpWrite(_) => {}
|
||||
Stream::Null(_) => {}
|
||||
Stream::Readline(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::StandardOutput(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
@@ -629,7 +650,7 @@ impl Stream {
|
||||
Stream::NamedTcp(ptr) => ptr.lines_read = value,
|
||||
Stream::NamedTls(ptr) => ptr.lines_read = value,
|
||||
Stream::HttpRead(ptr) => ptr.lines_read = value,
|
||||
Stream::HttpWrite(_) => {}
|
||||
Stream::HttpWrite(_) => {}
|
||||
Stream::Null(_) => {}
|
||||
Stream::Readline(ptr) => ptr.lines_read = value,
|
||||
Stream::StandardOutput(ptr) => ptr.lines_read = value,
|
||||
@@ -647,7 +668,7 @@ impl Stream {
|
||||
Stream::NamedTcp(ptr) => ptr.lines_read,
|
||||
Stream::NamedTls(ptr) => ptr.lines_read,
|
||||
Stream::HttpRead(ptr) => ptr.lines_read,
|
||||
Stream::HttpWrite(_) => 0,
|
||||
Stream::HttpWrite(_) => 0,
|
||||
Stream::Null(_) => 0,
|
||||
Stream::Readline(ptr) => ptr.lines_read,
|
||||
Stream::StandardOutput(ptr) => ptr.lines_read,
|
||||
@@ -669,7 +690,7 @@ impl CharRead for Stream {
|
||||
Stream::OutputFile(_) |
|
||||
Stream::StandardError(_) |
|
||||
Stream::StandardOutput(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::Null(_) => Some(Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
@@ -689,7 +710,7 @@ impl CharRead for Stream {
|
||||
Stream::OutputFile(_) |
|
||||
Stream::StandardError(_) |
|
||||
Stream::StandardOutput(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::Null(_) => Some(Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
@@ -709,7 +730,7 @@ impl CharRead for Stream {
|
||||
Stream::OutputFile(_) |
|
||||
Stream::StandardError(_) |
|
||||
Stream::StandardOutput(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::Null(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -726,7 +747,7 @@ impl CharRead for Stream {
|
||||
Stream::OutputFile(_) |
|
||||
Stream::StandardError(_) |
|
||||
Stream::StandardOutput(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::HttpWrite(_) |
|
||||
Stream::Null(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -744,13 +765,13 @@ impl Read for Stream {
|
||||
Stream::StaticString(src) => (*src).read(buf),
|
||||
Stream::Byte(cursor) => (*cursor).read(buf),
|
||||
Stream::OutputFile(_)
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::HttpWrite(_)
|
||||
| Stream::Null(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
)),
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::HttpWrite(_)
|
||||
| Stream::Null(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
)),
|
||||
};
|
||||
|
||||
bytes_read
|
||||
@@ -766,7 +787,7 @@ impl Write for Stream {
|
||||
Stream::Byte(ref mut cursor) => cursor.get_mut().write(buf),
|
||||
Stream::StandardOutput(stream) => stream.write(buf),
|
||||
Stream::StandardError(stream) => stream.write(buf),
|
||||
Stream::HttpWrite(ref mut stream) => stream.get_mut().write(buf),
|
||||
Stream::HttpWrite(ref mut stream) => stream.get_mut().write(buf),
|
||||
Stream::HttpRead(_) |
|
||||
Stream::StaticString(_) |
|
||||
Stream::Readline(_) |
|
||||
@@ -786,7 +807,7 @@ impl Write for Stream {
|
||||
Stream::Byte(ref mut cursor) => cursor.stream.get_mut().flush(),
|
||||
Stream::StandardError(stream) => stream.stream.flush(),
|
||||
Stream::StandardOutput(stream) => stream.stream.flush(),
|
||||
Stream::HttpWrite(ref mut stream) => stream.stream.get_mut().flush(),
|
||||
Stream::HttpWrite(ref mut stream) => stream.stream.get_mut().flush(),
|
||||
Stream::HttpRead(_) |
|
||||
Stream::StaticString(_) |
|
||||
Stream::Readline(_) |
|
||||
@@ -863,19 +884,38 @@ impl PartialEq for Stream {
|
||||
|
||||
impl Eq for Stream {}
|
||||
|
||||
fn cursor_position<T>(past_end_of_stream: &mut bool, cursor: &Cursor<T>, cursor_len: u64) -> AtEndOfStream {
|
||||
let position = cursor.position();
|
||||
|
||||
let at_end_of_stream = match position.cmp(&cursor_len) {
|
||||
Ordering::Equal => AtEndOfStream::At,
|
||||
Ordering::Greater => {
|
||||
*past_end_of_stream = true;
|
||||
AtEndOfStream::Past
|
||||
}
|
||||
Ordering::Less => AtEndOfStream::Not,
|
||||
};
|
||||
|
||||
at_end_of_stream
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
#[inline]
|
||||
pub(crate) fn position(&mut self) -> Option<(u64, usize)> {
|
||||
// returns lines_read, position.
|
||||
let result = match self {
|
||||
Stream::Byte(byte_stream_layout) => {
|
||||
Some(byte_stream_layout.stream.get_ref().0.position())
|
||||
}
|
||||
Stream::StaticString(string_stream_layout) => {
|
||||
Some(string_stream_layout.stream.stream.position())
|
||||
}
|
||||
Stream::InputFile(file_stream) => {
|
||||
file_stream.position()
|
||||
}
|
||||
Stream::NamedTcp(..)
|
||||
| Stream::NamedTls(..)
|
||||
| Stream::Readline(..)
|
||||
| Stream::StaticString(..)
|
||||
| Stream::Byte(..) => Some(0),
|
||||
Stream::NamedTcp(..) | Stream::NamedTls(..) | Stream::Readline(..) => {
|
||||
Some(0)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -913,7 +953,7 @@ impl Stream {
|
||||
Stream::NamedTcp(stream) => stream.past_end_of_stream,
|
||||
Stream::NamedTls(stream) => stream.past_end_of_stream,
|
||||
Stream::HttpRead(stream) => stream.past_end_of_stream,
|
||||
Stream::HttpWrite(stream) => stream.past_end_of_stream,
|
||||
Stream::HttpWrite(stream) => stream.past_end_of_stream,
|
||||
Stream::Null(_) => false,
|
||||
Stream::Readline(stream) => stream.past_end_of_stream,
|
||||
Stream::StandardOutput(stream) => stream.past_end_of_stream,
|
||||
@@ -936,7 +976,7 @@ impl Stream {
|
||||
Stream::NamedTcp(stream) => stream.past_end_of_stream = value,
|
||||
Stream::NamedTls(stream) => stream.past_end_of_stream = value,
|
||||
Stream::HttpRead(stream) => stream.past_end_of_stream = value,
|
||||
Stream::HttpWrite(stream) => stream.past_end_of_stream = value,
|
||||
Stream::HttpWrite(stream) => stream.past_end_of_stream = value,
|
||||
Stream::Null(_) => {}
|
||||
Stream::Readline(stream) => stream.past_end_of_stream = value,
|
||||
Stream::StandardOutput(stream) => stream.past_end_of_stream = value,
|
||||
@@ -950,38 +990,61 @@ impl Stream {
|
||||
return AtEndOfStream::Past;
|
||||
}
|
||||
|
||||
if let Stream::InputFile(stream_layout) = self {
|
||||
let position = stream_layout.position();
|
||||
match self {
|
||||
Stream::Byte(stream_layout) => {
|
||||
let StreamLayout {
|
||||
past_end_of_stream,
|
||||
stream,
|
||||
..
|
||||
} = &mut **stream_layout;
|
||||
|
||||
let StreamLayout {
|
||||
past_end_of_stream,
|
||||
stream,
|
||||
..
|
||||
} = &mut **stream_layout;
|
||||
let cursor_len = stream.get_ref().0.get_ref().len() as u64;
|
||||
cursor_position(past_end_of_stream, &stream.get_ref().0, cursor_len)
|
||||
}
|
||||
Stream::StaticString(stream_layout) => {
|
||||
let StreamLayout {
|
||||
past_end_of_stream,
|
||||
stream,
|
||||
..
|
||||
} = &mut **stream_layout;
|
||||
|
||||
match stream.get_ref().file.metadata() {
|
||||
Ok(metadata) => {
|
||||
if let Some(position) = position {
|
||||
return match position.cmp(&metadata.len()) {
|
||||
Ordering::Equal => AtEndOfStream::At,
|
||||
Ordering::Less => AtEndOfStream::Not,
|
||||
Ordering::Greater => {
|
||||
*past_end_of_stream = true;
|
||||
AtEndOfStream::Past
|
||||
let cursor_len = stream.stream.get_ref().len() as u64;
|
||||
cursor_position(past_end_of_stream, &stream.stream, cursor_len)
|
||||
}
|
||||
Stream::InputFile(stream_layout) => {
|
||||
let position = stream_layout.position();
|
||||
|
||||
let StreamLayout {
|
||||
past_end_of_stream,
|
||||
stream,
|
||||
..
|
||||
} = &mut **stream_layout;
|
||||
|
||||
match stream.get_ref().file.metadata() {
|
||||
Ok(metadata) => {
|
||||
if let Some(position) = position {
|
||||
match position.cmp(&metadata.len()) {
|
||||
Ordering::Equal => AtEndOfStream::At,
|
||||
Ordering::Less => AtEndOfStream::Not,
|
||||
Ordering::Greater => {
|
||||
*past_end_of_stream = true;
|
||||
AtEndOfStream::Past
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
} else {
|
||||
*past_end_of_stream = true;
|
||||
AtEndOfStream::Past
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
*past_end_of_stream = true;
|
||||
AtEndOfStream::Past
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
*past_end_of_stream = true;
|
||||
AtEndOfStream::Past
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AtEndOfStream::Not
|
||||
_ => {
|
||||
AtEndOfStream::Not
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,10 +1063,10 @@ impl Stream {
|
||||
pub(crate) fn mode(&self) -> Atom {
|
||||
match self {
|
||||
Stream::Byte(_)
|
||||
| Stream::Readline(_)
|
||||
| Stream::StaticString(_)
|
||||
| Stream::HttpRead(_)
|
||||
| Stream::InputFile(..) => atom!("read"),
|
||||
| Stream::Readline(_)
|
||||
| Stream::StaticString(_)
|
||||
| Stream::HttpRead(_)
|
||||
| Stream::InputFile(..) => atom!("read"),
|
||||
Stream::NamedTcp(..) | Stream::NamedTls(..) => atom!("read_append"),
|
||||
Stream::OutputFile(file) if file.is_append => atom!("append"),
|
||||
Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::HttpWrite(_) => atom!("write"),
|
||||
@@ -1077,12 +1140,17 @@ impl Stream {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_http_sender(
|
||||
body_writer: Sender,
|
||||
response: TypedArenaPtr<HttpResponse>,
|
||||
status_code: u16,
|
||||
headers: hyper::HeaderMap,
|
||||
arena: &mut Arena,
|
||||
) -> Self {
|
||||
Stream::HttpWrite(arena_alloc!(
|
||||
StreamLayout::new(CharReader::new(HttpWriteStream {
|
||||
body_writer
|
||||
response,
|
||||
status_code,
|
||||
headers,
|
||||
buffer: Vec::new(),
|
||||
})),
|
||||
arena
|
||||
))
|
||||
@@ -1135,11 +1203,11 @@ impl Stream {
|
||||
Stream::HttpWrite(ref mut http_stream) => {
|
||||
unsafe {
|
||||
http_stream.set_tag(ArenaHeaderTag::Dropped);
|
||||
std::ptr::drop_in_place(&mut http_stream.inner_mut().body_writer as *mut _);
|
||||
std::ptr::drop_in_place(&mut http_stream.inner_mut().buffer as *mut _);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Stream::InputFile(mut file_stream) => {
|
||||
// close the stream by dropping the inner File.
|
||||
unsafe {
|
||||
@@ -1175,12 +1243,12 @@ impl Stream {
|
||||
pub(crate) fn is_input_stream(&self) -> bool {
|
||||
match self {
|
||||
Stream::NamedTcp(..)
|
||||
| Stream::NamedTls(..)
|
||||
| Stream::HttpRead(..)
|
||||
| Stream::Byte(_)
|
||||
| Stream::Readline(_)
|
||||
| Stream::StaticString(_)
|
||||
| Stream::InputFile(..) => true,
|
||||
| Stream::NamedTls(..)
|
||||
| Stream::HttpRead(..)
|
||||
| Stream::Byte(_)
|
||||
| Stream::Readline(_)
|
||||
| Stream::StaticString(_)
|
||||
| Stream::InputFile(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -1189,12 +1257,12 @@ impl Stream {
|
||||
pub(crate) fn is_output_stream(&self) -> bool {
|
||||
match self {
|
||||
Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::NamedTcp(..)
|
||||
| Stream::NamedTls(..)
|
||||
| Stream::HttpWrite(..)
|
||||
| Stream::Byte(_)
|
||||
| Stream::OutputFile(..) => true,
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::NamedTcp(..)
|
||||
| Stream::NamedTls(..)
|
||||
| Stream::HttpWrite(..)
|
||||
| Stream::Byte(_)
|
||||
| Stream::OutputFile(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -1242,12 +1310,9 @@ impl Stream {
|
||||
}
|
||||
}
|
||||
Stream::InputFile(ref mut file) => {
|
||||
let mut b = [0u8; 1];
|
||||
|
||||
match file.read(&mut b)? {
|
||||
1 => {
|
||||
file.stream.get_mut().file.seek(SeekFrom::Current(-1))?;
|
||||
Ok(b[0])
|
||||
match file.peek_byte() {
|
||||
Some(result) => {
|
||||
Ok(result?)
|
||||
}
|
||||
_ => Err(std::io::Error::new(
|
||||
ErrorKind::UnexpectedEof,
|
||||
@@ -1283,7 +1348,7 @@ impl MachineState {
|
||||
match eof_action {
|
||||
EOFAction::Error => {
|
||||
stream.set_past_end_of_stream(true);
|
||||
return Err(self.open_past_eos_error(stream, caller, arity));
|
||||
Err(self.open_past_eos_error(stream, caller, arity))
|
||||
}
|
||||
EOFAction::EOFCode => {
|
||||
let end_of_stream = if stream.options().stream_type() == StreamType::Binary {
|
||||
@@ -1313,101 +1378,101 @@ impl MachineState {
|
||||
stream_type: HeapCellValue,
|
||||
) -> StreamOptions {
|
||||
let alias = read_heap_cell!(self.store(MachineState::deref(self, alias)),
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
|
||||
if name != atom!("[]") {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
if name != atom!("[]") {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
debug_assert_eq!(arity, 0);
|
||||
|
||||
if name != atom!("[]") {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
if name != atom!("[]") {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
);
|
||||
|
||||
let eof_action = read_heap_cell!(self.store(MachineState::deref(self, eof_action)),
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
|
||||
match name {
|
||||
atom!("eof_code") => EOFAction::EOFCode,
|
||||
atom!("error") => EOFAction::Error,
|
||||
atom!("reset") => EOFAction::Reset,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
match name {
|
||||
atom!("eof_code") => EOFAction::EOFCode,
|
||||
atom!("error") => EOFAction::Error,
|
||||
atom!("reset") => EOFAction::Reset,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
debug_assert_eq!(arity, 0);
|
||||
|
||||
match name {
|
||||
atom!("eof_code") => EOFAction::EOFCode,
|
||||
atom!("error") => EOFAction::Error,
|
||||
atom!("reset") => EOFAction::Reset,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
match name {
|
||||
atom!("eof_code") => EOFAction::EOFCode,
|
||||
atom!("error") => EOFAction::Error,
|
||||
atom!("reset") => EOFAction::Reset,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
);
|
||||
|
||||
let reposition = read_heap_cell!(self.store(MachineState::deref(self, reposition)),
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
debug_assert_eq!(arity, 0);
|
||||
name == atom!("true")
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
);
|
||||
|
||||
let stream_type = read_heap_cell!(self.store(MachineState::deref(self, stream_type)),
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
match name {
|
||||
atom!("text") => StreamType::Text,
|
||||
atom!("binary") => StreamType::Binary,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
match name {
|
||||
atom!("text") => StreamType::Text,
|
||||
atom!("binary") => StreamType::Binary,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
match name {
|
||||
atom!("text") => StreamType::Text,
|
||||
atom!("binary") => StreamType::Binary,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
debug_assert_eq!(arity, 0);
|
||||
match name {
|
||||
atom!("text") => StreamType::Text,
|
||||
atom!("binary") => StreamType::Binary,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
);
|
||||
|
||||
let mut options = StreamOptions::default();
|
||||
@@ -1430,60 +1495,60 @@ impl MachineState {
|
||||
let addr = self.store(MachineState::deref(self, addr));
|
||||
|
||||
read_heap_cell!(addr,
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
|
||||
return match stream_aliases.get(&name) {
|
||||
Some(stream) if !stream.is_null_stream() => Ok(*stream),
|
||||
_ => {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let addr = atom_as_cell!(name);
|
||||
return match stream_aliases.get(&name) {
|
||||
Some(stream) if !stream.is_null_stream() => Ok(*stream),
|
||||
_ => {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let addr = atom_as_cell!(name);
|
||||
|
||||
let existence_error = self.existence_error(ExistenceError::Stream(addr));
|
||||
let existence_error = self.existence_error(ExistenceError::Stream(addr));
|
||||
|
||||
Err(self.error_form(existence_error, stub))
|
||||
}
|
||||
};
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
Err(self.error_form(existence_error, stub))
|
||||
}
|
||||
};
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
debug_assert_eq!(arity, 0);
|
||||
debug_assert_eq!(arity, 0);
|
||||
|
||||
return match stream_aliases.get(&name) {
|
||||
Some(stream) if !stream.is_null_stream() => Ok(*stream),
|
||||
_ => {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let addr = atom_as_cell!(name);
|
||||
return match stream_aliases.get(&name) {
|
||||
Some(stream) if !stream.is_null_stream() => Ok(*stream),
|
||||
_ => {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let addr = atom_as_cell!(name);
|
||||
|
||||
let existence_error = self.existence_error(ExistenceError::Stream(addr));
|
||||
let existence_error = self.existence_error(ExistenceError::Stream(addr));
|
||||
|
||||
Err(self.error_form(existence_error, stub))
|
||||
}
|
||||
};
|
||||
}
|
||||
(HeapCellValueTag::Cons, ptr) => {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::Stream, stream) => {
|
||||
return if stream.is_null_stream() {
|
||||
Err(self.open_permission_error(stream_as_cell!(stream), caller, arity))
|
||||
} else {
|
||||
Ok(stream)
|
||||
};
|
||||
}
|
||||
(ArenaHeaderTag::Dropped, _value) => {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let err = self.existence_error(ExistenceError::Stream(addr));
|
||||
Err(self.error_form(existence_error, stub))
|
||||
}
|
||||
};
|
||||
}
|
||||
(HeapCellValueTag::Cons, ptr) => {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::Stream, stream) => {
|
||||
return if stream.is_null_stream() {
|
||||
Err(self.open_permission_error(stream_as_cell!(stream), caller, arity))
|
||||
} else {
|
||||
Ok(stream)
|
||||
};
|
||||
}
|
||||
(ArenaHeaderTag::Dropped, _value) => {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let err = self.existence_error(ExistenceError::Stream(addr));
|
||||
|
||||
return Err(self.error_form(err, stub));
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
return Err(self.error_form(err, stub));
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
|
||||
let stub = functor_stub(caller, arity);
|
||||
@@ -1497,20 +1562,10 @@ impl MachineState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn open_parsing_stream(
|
||||
&mut self,
|
||||
mut stream: Stream,
|
||||
stub_name: Atom,
|
||||
stub_arity: usize,
|
||||
) -> Result<Stream, MachineStub> {
|
||||
pub(crate) fn open_parsing_stream(&mut self, mut stream: Stream) -> Result<Stream, ParserError> {
|
||||
match stream.peek_char() {
|
||||
None => Ok(stream), // empty stream is handled gracefully by Lexer::eof
|
||||
Some(Err(e)) => {
|
||||
let err = self.session_error(SessionError::from(e));
|
||||
let stub = functor_stub(stub_name, stub_arity);
|
||||
|
||||
Err(self.error_form(err, stub))
|
||||
}
|
||||
Some(Err(e)) => Err(ParserError::IO(e)),
|
||||
Some(Ok(c)) => {
|
||||
if c == '\u{feff}' {
|
||||
// skip UTF-8 BOM
|
||||
@@ -1531,7 +1586,15 @@ impl MachineState {
|
||||
arity: usize,
|
||||
) -> MachineStub {
|
||||
let stub = functor_stub(caller, arity);
|
||||
let err = self.permission_error(perm, err_atom, stream_as_cell!(stream));
|
||||
let err = self.permission_error(
|
||||
perm,
|
||||
err_atom,
|
||||
if let Some(alias) = stream.options().get_alias() {
|
||||
atom_as_cell!(alias)
|
||||
} else {
|
||||
stream_as_cell!(stream)
|
||||
},
|
||||
);
|
||||
|
||||
self.error_form(err, stub)
|
||||
}
|
||||
@@ -1699,7 +1762,7 @@ impl MachineState {
|
||||
}
|
||||
ErrorKind::PermissionDenied => {
|
||||
// 8.11.5.3k)
|
||||
return Err(self.open_permission_error(self[temp_v!(1)], atom!("open"), 4));
|
||||
return Err(self.open_permission_error(self.registers[1], atom!("open"), 4));
|
||||
}
|
||||
_ => {
|
||||
let stub = functor_stub(atom!("open"), 4);
|
||||
|
||||
+1753
-892
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ use crate::machine::loader::*;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::parser::*;
|
||||
use crate::read::devour_whitespace;
|
||||
|
||||
use crate::predicate_queue;
|
||||
|
||||
@@ -52,14 +53,14 @@ impl<'a> TermStream for BootstrappingTermStream<'a> {
|
||||
fn next(&mut self, op_dir: &CompositeOpDir) -> Result<Term, CompilationError> {
|
||||
self.parser.reset();
|
||||
self.parser
|
||||
.read_term(op_dir)
|
||||
.read_term(op_dir, Tokens::Default)
|
||||
.map_err(CompilationError::from)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn eof(&mut self) -> Result<bool, CompilationError> {
|
||||
self.parser.devour_whitespace()?; // eliminate dangling comments before checking for EOF.
|
||||
Ok(self.parser.eof()?)
|
||||
devour_whitespace(&mut self.parser) // eliminate dangling comments before checking for EOF.
|
||||
.map_err(CompilationError::from)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -111,7 +112,7 @@ impl TermStream for LiveTermStream {
|
||||
|
||||
#[inline]
|
||||
fn eof(&mut self) -> Result<bool, CompilationError> {
|
||||
return Ok(self.term_queue.is_empty());
|
||||
Ok(self.term_queue.is_empty())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -125,15 +126,15 @@ pub struct InlineTermStream {
|
||||
|
||||
impl TermStream for InlineTermStream {
|
||||
fn next(&mut self, _: &CompositeOpDir) -> Result<Term, CompilationError> {
|
||||
Err(CompilationError::from(ParserError::UnexpectedEOF))
|
||||
Err(CompilationError::from(ParserError::unexpected_eof()))
|
||||
}
|
||||
|
||||
fn eof(&mut self) -> Result<bool, CompilationError> {
|
||||
Ok(true)
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn listing_src(&self) -> &ListingSource {
|
||||
&ListingSource::User
|
||||
&ListingSource::User
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,788 @@
|
||||
use crate::arena::*;
|
||||
use crate::forms::*;
|
||||
use crate::heap_iter::stackful_preorder_iter;
|
||||
use crate::machine::*;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::machine::partial_string::*;
|
||||
use crate::types::*;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use derive_deref::*;
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
pub(crate) trait Unifier: DerefMut<Target = MachineState> {
|
||||
fn unify_structure(&mut self, s1: usize, value: HeapCellValue) {
|
||||
// s1 is the value of a STR cell.
|
||||
let (n1, a1) = cell_as_atom_cell!(self.heap[s1]).get_name_and_arity();
|
||||
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Str, s2) => {
|
||||
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
|
||||
.get_name_and_arity();
|
||||
|
||||
if n1 == n2 && a1 == a2 {
|
||||
for idx in (0..a1).rev() {
|
||||
self.pdl.push(heap_loc_as_cell!(s2+1+idx));
|
||||
self.pdl.push(heap_loc_as_cell!(s1+1+idx));
|
||||
}
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Lis, l2) => {
|
||||
if a1 == 2 && n1 == atom!(".") {
|
||||
for idx in (0..2).rev() {
|
||||
self.pdl.push(heap_loc_as_cell!(l2+1+idx));
|
||||
self.pdl.push(heap_loc_as_cell!(s1+1+idx));
|
||||
}
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Atom, (n2, a2)) => {
|
||||
self.fail = !(a1 == 0 && a2 == 0 && n1 == n2);
|
||||
}
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
Self::bind(self, Ref::attr_var(h), str_loc_as_cell!(s1));
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
Self::bind(self, Ref::heap_cell(h), str_loc_as_cell!(s1));
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
Self::bind(self, Ref::stack_cell(s), str_loc_as_cell!(s1));
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn unify_list(&mut self, l1: usize, value: HeapCellValue) {
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Lis, l2) => {
|
||||
for idx in (0..2).rev() {
|
||||
self.pdl.push(heap_loc_as_cell!(l2 + idx));
|
||||
self.pdl.push(heap_loc_as_cell!(l1 + idx));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s2) => {
|
||||
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
|
||||
.get_name_and_arity();
|
||||
|
||||
if a2 == 2 && n2 == atom!(".") {
|
||||
for idx in (0..2).rev() {
|
||||
self.pdl.push(heap_loc_as_cell!(s2+1+idx));
|
||||
self.pdl.push(heap_loc_as_cell!(l1+idx));
|
||||
}
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStrLoc | HeapCellValueTag::CStr | HeapCellValueTag::PStr) => {
|
||||
Self::unify_partial_string(self, list_loc_as_cell!(l1), value)
|
||||
}
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
Self::bind(self, Ref::attr_var(h), list_loc_as_cell!(l1));
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
Self::bind(self, Ref::heap_cell(h), list_loc_as_cell!(l1));
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
Self::bind(self, Ref::stack_cell(s), list_loc_as_cell!(l1));
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn unify_complete_string(&mut self, atom: Atom, value: HeapCellValue) {
|
||||
if let Some(r) = value.as_var() {
|
||||
if atom == atom!("") {
|
||||
Self::bind(self, r, atom_as_cell!(atom!("[]")));
|
||||
} else {
|
||||
Self::bind(self, r, atom_as_cstr_cell!(atom));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Atom, (cstr_atom, arity)) if atom == atom!("") => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
self.fail = cstr_atom != atom!("[]");
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
if arity == 0 {
|
||||
self.fail = atom == atom!("") && name != atom!("[]");
|
||||
} else {
|
||||
// this is intentionally the same policy for
|
||||
// value.tag() == Lis and PStrLoc. they're not
|
||||
// grouped together to allow for arity == 0.
|
||||
Self::unify_partial_string(self, atom_as_cstr_cell!(atom), value);
|
||||
|
||||
if !self.pdl.is_empty() {
|
||||
Self::unify_internal(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::CStr, cstr_atom) => {
|
||||
self.fail = atom != cstr_atom;
|
||||
}
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc) => {
|
||||
Self::unify_partial_string(self, atom_as_cstr_cell!(atom), value);
|
||||
|
||||
if !self.pdl.is_empty() {
|
||||
Self::unify_internal(self);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// the return value of unify_partial_string is interpreted as
|
||||
// follows:
|
||||
//
|
||||
// Some(None) -- the strings are equal, nothing to unify
|
||||
// Some(Some(f2,f1)) -- prefixes equal, try to unify focus values f2, f1
|
||||
// None -- prefixes not equal, unification fails
|
||||
//
|
||||
// d1's tag is assumed to be one of LIS, STR or PSTRLOC.
|
||||
fn unify_partial_string(&mut self, value_1: HeapCellValue, value_2: HeapCellValue) {
|
||||
if let Some(r) = value_2.as_var() {
|
||||
Self::bind(self, r, value_1);
|
||||
return;
|
||||
}
|
||||
|
||||
let machine_st = self.deref_mut();
|
||||
|
||||
let s1 = machine_st.heap.len();
|
||||
|
||||
machine_st.heap.push(value_1);
|
||||
machine_st.heap.push(value_2);
|
||||
|
||||
let mut pstr_iter1 = HeapPStrIter::new(&machine_st.heap, s1);
|
||||
let mut pstr_iter2 = HeapPStrIter::new(&machine_st.heap, s1 + 1);
|
||||
|
||||
match compare_pstr_prefixes(&mut pstr_iter1, &mut pstr_iter2) {
|
||||
PStrCmpResult::Ordered(Ordering::Equal) => {}
|
||||
PStrCmpResult::Ordered(Ordering::Less) => {
|
||||
if pstr_iter2.focus.as_var().is_none() {
|
||||
machine_st.fail = true;
|
||||
} else {
|
||||
machine_st.pdl.push(empty_list_as_cell!());
|
||||
machine_st.pdl.push(pstr_iter2.focus);
|
||||
}
|
||||
}
|
||||
PStrCmpResult::Ordered(Ordering::Greater) => {
|
||||
if pstr_iter1.focus.as_var().is_none() {
|
||||
machine_st.fail = true;
|
||||
} else {
|
||||
machine_st.pdl.push(empty_list_as_cell!());
|
||||
machine_st.pdl.push(pstr_iter1.focus);
|
||||
}
|
||||
}
|
||||
continuable @ PStrCmpResult::FirstIterContinuable(iteratee) |
|
||||
continuable @ PStrCmpResult::SecondIterContinuable(iteratee) => {
|
||||
if continuable.is_second_iter() {
|
||||
std::mem::swap(&mut pstr_iter1, &mut pstr_iter2);
|
||||
}
|
||||
|
||||
let mut chars_iter = PStrCharsIter {
|
||||
iter: pstr_iter1,
|
||||
item: Some(iteratee),
|
||||
};
|
||||
|
||||
let mut focus = pstr_iter2.focus;
|
||||
|
||||
'outer: loop {
|
||||
while let Some(c) = chars_iter.peek() {
|
||||
read_heap_cell!(focus,
|
||||
(HeapCellValueTag::Lis, l) => {
|
||||
let val = pstr_iter2.heap[l];
|
||||
|
||||
machine_st.pdl.push(val);
|
||||
machine_st.pdl.push(char_as_cell!(c));
|
||||
|
||||
focus = pstr_iter2.heap[l+1];
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(pstr_iter2.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
if name == atom!(".") && arity == 2 {
|
||||
machine_st.pdl.push(pstr_iter2.heap[s+1]);
|
||||
machine_st.pdl.push(char_as_cell!(c));
|
||||
|
||||
focus = pstr_iter2.heap[s+2];
|
||||
} else {
|
||||
machine_st.fail = true;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var, h) => {
|
||||
match chars_iter.item.unwrap() {
|
||||
PStrIteratee::Char(focus, _) => {
|
||||
machine_st.pdl.push(machine_st.heap[focus]);
|
||||
machine_st.pdl.push(heap_loc_as_cell!(h));
|
||||
}
|
||||
PStrIteratee::PStrSegment(focus, _, n) => {
|
||||
read_heap_cell!(machine_st.heap[focus],
|
||||
(HeapCellValueTag::CStr | HeapCellValueTag::PStr, pstr_atom) => {
|
||||
if focus < machine_st.heap.len() - 2 {
|
||||
machine_st.heap.pop();
|
||||
machine_st.heap.pop();
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
let target_cell = match machine_st.heap[focus].get_tag() {
|
||||
HeapCellValueTag::CStr => {
|
||||
atom_as_cstr_cell!(pstr_atom)
|
||||
}
|
||||
HeapCellValueTag::PStr => {
|
||||
pstr_loc_as_cell!(focus)
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
machine_st.pdl.push(target_cell);
|
||||
machine_st.pdl.push(heap_loc_as_cell!(h));
|
||||
} else {
|
||||
let h_len = machine_st.heap.len();
|
||||
|
||||
machine_st.heap.push(pstr_offset_as_cell!(focus));
|
||||
machine_st.heap.push(fixnum_as_cell!(
|
||||
Fixnum::build_with(n as i64)
|
||||
));
|
||||
|
||||
machine_st.pdl.push(pstr_loc_as_cell!(h_len));
|
||||
machine_st.pdl.push(heap_loc_as_cell!(h));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
(HeapCellValueTag::PStrOffset, pstr_loc) => {
|
||||
let n0 = cell_as_fixnum!(machine_st.heap[focus+1])
|
||||
.get_num() as usize;
|
||||
|
||||
if pstr_loc < machine_st.heap.len() - 2 {
|
||||
machine_st.heap.pop();
|
||||
machine_st.heap.pop();
|
||||
}
|
||||
|
||||
if n == n0 {
|
||||
machine_st.pdl.push(pstr_loc_as_cell!(focus));
|
||||
machine_st.pdl.push(heap_loc_as_cell!(h));
|
||||
} else {
|
||||
let h_len = machine_st.heap.len();
|
||||
|
||||
machine_st.heap.push(pstr_offset_as_cell!(pstr_loc));
|
||||
machine_st.heap.push(fixnum_as_cell!(
|
||||
Fixnum::build_with(n as i64)
|
||||
));
|
||||
|
||||
machine_st.pdl.push(pstr_loc_as_cell!(h_len));
|
||||
machine_st.pdl.push(heap_loc_as_cell!(h));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
|
||||
if focus < machine_st.heap.len() - 2 {
|
||||
machine_st.heap.pop();
|
||||
machine_st.heap.pop();
|
||||
}
|
||||
|
||||
machine_st.pdl.push(machine_st.heap[focus]);
|
||||
machine_st.pdl.push(heap_loc_as_cell!(h));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break 'outer;
|
||||
}
|
||||
_ => {
|
||||
machine_st.fail = true;
|
||||
break 'outer;
|
||||
}
|
||||
);
|
||||
|
||||
chars_iter.next();
|
||||
}
|
||||
|
||||
chars_iter.iter.next();
|
||||
|
||||
machine_st.pdl.push(focus);
|
||||
machine_st.pdl.push(chars_iter.iter.focus);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
PStrCmpResult::Unordered => {
|
||||
machine_st.pdl.push(pstr_iter1.focus);
|
||||
machine_st.pdl.push(pstr_iter2.focus);
|
||||
}
|
||||
}
|
||||
|
||||
machine_st.heap.pop();
|
||||
machine_st.heap.pop();
|
||||
}
|
||||
|
||||
fn unify_atom(&mut self, atom: Atom, value: HeapCellValue) {
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
self.fail = !(arity == 0 && name == atom);
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
self.fail = !(arity == 0 && name == atom);
|
||||
}
|
||||
(HeapCellValueTag::CStr, cstr_atom) if atom == atom!("[]") => {
|
||||
self.fail = cstr_atom != atom!("");
|
||||
}
|
||||
(HeapCellValueTag::Char, c1) => {
|
||||
if let Some(c2) = atom.as_char() {
|
||||
self.fail = c1 != c2;
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
Self::bind(self, Ref::attr_var(h), atom_as_cell!(atom));
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
Self::bind(self, Ref::heap_cell(h), atom_as_cell!(atom));
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
Self::bind(self, Ref::stack_cell(s), atom_as_cell!(atom));
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn unify_char(&mut self, c: char, value: HeapCellValue) {
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
if let Some(c2) = name.as_char() {
|
||||
self.fail = !(c == c2 && arity == 0);
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
let (name, arity) = cell_as_atom_cell!(self.heap[s])
|
||||
.get_name_and_arity();
|
||||
|
||||
if let Some(c2) = name.as_char() {
|
||||
self.fail = !(c == c2 && arity == 0);
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Char, c2) => {
|
||||
if c != c2 {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
Self::bind(self, Ref::attr_var(h), char_as_cell!(c));
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
Self::bind(self, Ref::heap_cell(h), char_as_cell!(c));
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
Self::bind(self, Ref::stack_cell(s), char_as_cell!(c));
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn unify_fixnum(&mut self, n1: Fixnum, value: HeapCellValue) {
|
||||
if let Some(r) = value.as_var() {
|
||||
Self::bind(self, r, fixnum_as_cell!(n1));
|
||||
return;
|
||||
}
|
||||
|
||||
match Number::try_from(value) {
|
||||
Ok(n2) => match n2 {
|
||||
Number::Fixnum(n2) if n1.get_num() == n2.get_num() => {}
|
||||
Number::Integer(n2) if n1.get_num() == *n2 => {}
|
||||
Number::Rational(n2) if n1.get_num() == *n2 => {}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_big_num<N>(&mut self, n1: TypedArenaPtr<N>, value: HeapCellValue)
|
||||
where N: PartialEq<Rational>
|
||||
+ PartialEq<Integer>
|
||||
+ PartialEq<i64>
|
||||
+ ArenaAllocated
|
||||
{
|
||||
if let Some(r) = value.as_var() {
|
||||
Self::bind(self, r, typed_arena_ptr_as_cell!(n1));
|
||||
return;
|
||||
}
|
||||
|
||||
match Number::try_from(value) {
|
||||
Ok(n2) => match n2 {
|
||||
Number::Fixnum(n2) if *n1 == n2.get_num() => {}
|
||||
Number::Integer(n2) if *n1 == *n2 => {}
|
||||
Number::Rational(n2) if *n1 == *n2 => {}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_f64(&mut self, f1: F64Ptr, value: HeapCellValue) {
|
||||
if let Some(r) = value.as_var() {
|
||||
Self::bind(self, r, HeapCellValue::from(f1));
|
||||
return;
|
||||
}
|
||||
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::F64, f2) => {
|
||||
self.fail = **f1 != **f2;
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn unify_constant(&mut self, ptr: UntypedArenaPtr, value: HeapCellValue) {
|
||||
if let Some(ptr2) = value.to_untyped_arena_ptr() {
|
||||
if ptr.get_ptr() == ptr2.get_ptr() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::Integer, int_ptr) => {
|
||||
Self::unify_big_num(self, int_ptr, value);
|
||||
}
|
||||
(ArenaHeaderTag::Rational, rat_ptr) => {
|
||||
Self::unify_big_num(self, rat_ptr, value);
|
||||
}
|
||||
(ArenaHeaderTag::Stream, stream) => {
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var | HeapCellValueTag::StackVar) => {
|
||||
Self::bind(self, value.as_var().unwrap(), untyped_arena_ptr_as_cell!(ptr));
|
||||
}
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
if arity > 0 {
|
||||
self.fail = true;
|
||||
} else {
|
||||
let stream_options = stream.options();
|
||||
|
||||
if let Some(alias) = stream_options.get_alias() {
|
||||
self.fail = name != alias;
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
if let Some(r) = value.as_var() {
|
||||
Self::bind(self, r, untyped_arena_ptr_as_cell!(ptr));
|
||||
} else {
|
||||
self.fail = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn unify_internal(&mut self) {
|
||||
let mut tabu_list = IndexSet::with_hasher(FxBuildHasher::default());
|
||||
|
||||
while !(self.pdl.is_empty() || self.fail) {
|
||||
let s1 = self.pdl.pop().unwrap();
|
||||
let s1 = (self.deref() as &MachineState).deref(s1);
|
||||
|
||||
let s2 = self.pdl.pop().unwrap();
|
||||
let s2 = (self.deref() as &MachineState).deref(s2);
|
||||
|
||||
if s1 != s2 {
|
||||
let d1 = self.store(s1);
|
||||
let d2 = self.store(s2);
|
||||
|
||||
read_heap_cell!(d1,
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
Self::bind(self, Ref::attr_var(h), d2);
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
Self::bind(self, Ref::heap_cell(h), d2);
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
Self::bind(self, Ref::stack_cell(s), d2);
|
||||
}
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
debug_assert_eq!(arity, 0);
|
||||
Self::unify_atom(self, name, d2);
|
||||
}
|
||||
(HeapCellValueTag::Str, s1) => {
|
||||
if tabu_list.contains(&(d1, d2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Self::unify_structure(self, s1, d2);
|
||||
|
||||
if !self.fail {
|
||||
let d2 = self.store(d2);
|
||||
tabu_list.insert((d1, d2));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Lis, l1) => {
|
||||
if d2.is_ref() {
|
||||
if tabu_list.contains(&(d1, d2)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Self::unify_list(self, l1, d2);
|
||||
|
||||
if !self.fail {
|
||||
let d2 = self.store(d2);
|
||||
tabu_list.insert((d1, d2));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStrLoc) => {
|
||||
read_heap_cell!(d2,
|
||||
(HeapCellValueTag::PStrLoc |
|
||||
HeapCellValueTag::Lis |
|
||||
HeapCellValueTag::Str) => {
|
||||
if tabu_list.contains(&(d1, d2)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::CStr |
|
||||
HeapCellValueTag::AttrVar |
|
||||
HeapCellValueTag::Var |
|
||||
HeapCellValueTag::StackVar) => {
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
break;
|
||||
}
|
||||
);
|
||||
|
||||
Self::unify_partial_string(self, d1, d2);
|
||||
|
||||
if !self.fail && !d2.is_constant() {
|
||||
let d2 = self.store(d2);
|
||||
tabu_list.insert((d1, d2));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::CStr) => {
|
||||
read_heap_cell!(d2,
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
Self::bind(self, Ref::attr_var(h), d1);
|
||||
continue;
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
Self::bind(self, Ref::heap_cell(h), d1);
|
||||
continue;
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
Self::bind(self, Ref::stack_cell(s), d1);
|
||||
continue;
|
||||
}
|
||||
(HeapCellValueTag::Str |
|
||||
HeapCellValueTag::Lis |
|
||||
HeapCellValueTag::PStrLoc) => {
|
||||
}
|
||||
(HeapCellValueTag::CStr) => {
|
||||
self.fail = d1 != d2;
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
self.fail = true;
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
Self::unify_partial_string(self, d2, d1);
|
||||
}
|
||||
(HeapCellValueTag::F64, f1) => {
|
||||
Self::unify_f64(self, f1, d2);
|
||||
}
|
||||
(HeapCellValueTag::Fixnum, n1) => {
|
||||
Self::unify_fixnum(self, n1, d2);
|
||||
}
|
||||
(HeapCellValueTag::Char, c1) => {
|
||||
Self::unify_char(self, c1, d2);
|
||||
}
|
||||
(HeapCellValueTag::Cons, ptr_1) => {
|
||||
Self::unify_constant(self, ptr_1, d2);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bind(&mut self, r: Ref, value: HeapCellValue);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bind_with_occurs_check<U: Unifier>(unifier: &mut U, r: Ref, value: HeapCellValue) -> bool {
|
||||
if let RefTag::StackCell = r.get_tag() {
|
||||
// local variable optimization -- r cannot occur in the
|
||||
// heap structure bound to value, so don't bother
|
||||
// traversing value.
|
||||
U::bind(unifier, r, value);
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut occurs_triggered = false;
|
||||
|
||||
if !value.is_constant() {
|
||||
let machine_st: &mut MachineState = unifier.deref_mut();
|
||||
|
||||
for cell in stackful_preorder_iter(&mut machine_st.heap, &mut machine_st.stack, value) {
|
||||
let cell = unmark_cell_bits!(cell);
|
||||
|
||||
if let Some(inner_r) = cell.as_var() {
|
||||
if r == inner_r {
|
||||
occurs_triggered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if occurs_triggered {
|
||||
unifier.fail = true;
|
||||
} else {
|
||||
U::bind(unifier, r, value);
|
||||
}
|
||||
|
||||
return occurs_triggered;
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub(crate) struct DefaultUnifier<'a> {
|
||||
machine_st: &'a mut MachineState,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mut MachineState> for DefaultUnifier<'a> {
|
||||
#[inline(always)]
|
||||
fn from(machine_st: &'a mut MachineState) -> Self {
|
||||
Self { machine_st }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Unifier for DefaultUnifier<'a> {
|
||||
fn bind(&mut self, r: Ref, value: HeapCellValue) {
|
||||
self.machine_st.bind(r, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CompositeUnifierForOccursCheck<U> {
|
||||
unifier: U,
|
||||
}
|
||||
|
||||
impl<U: Unifier> Deref for CompositeUnifierForOccursCheck<U> {
|
||||
type Target = MachineState;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.unifier.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unifier> DerefMut for CompositeUnifierForOccursCheck<U> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.unifier.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unifier> From<U> for CompositeUnifierForOccursCheck<U> {
|
||||
#[inline(always)]
|
||||
fn from(unifier: U) -> Self {
|
||||
Self { unifier }
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unifier> Unifier for CompositeUnifierForOccursCheck<U> {
|
||||
fn bind(&mut self, r: Ref, value: HeapCellValue) {
|
||||
bind_with_occurs_check(&mut self.unifier, r, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CompositeUnifierForOccursCheckWithError<U: Unifier> {
|
||||
unifier: U,
|
||||
}
|
||||
|
||||
impl<U: Unifier> Deref for CompositeUnifierForOccursCheckWithError<U> {
|
||||
type Target = MachineState;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.unifier.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unifier> DerefMut for CompositeUnifierForOccursCheckWithError<U> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.unifier.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unifier> From<U> for CompositeUnifierForOccursCheckWithError<U> {
|
||||
#[inline(always)]
|
||||
fn from(unifier: U) -> Self {
|
||||
Self { unifier }
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unifier> Unifier for CompositeUnifierForOccursCheckWithError<U> {
|
||||
fn bind(&mut self, r: Ref, value: HeapCellValue) {
|
||||
if bind_with_occurs_check(&mut self.unifier, r, value) {
|
||||
let err = self.representation_error(RepFlag::Term);
|
||||
let stub = functor_stub(atom!("unify_with_occurs_check"), 2);
|
||||
let err = self.error_form(err, stub);
|
||||
|
||||
self.throw_exception(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-19
@@ -15,7 +15,7 @@ macro_rules! char_as_cell {
|
||||
|
||||
macro_rules! fixnum_as_cell {
|
||||
($n: expr) => {
|
||||
HeapCellValue::from_bytes($n.into_bytes()) //HeapCellValueTag::Fixnum, $n.get_num() as u64)
|
||||
HeapCellValue::from_bytes($n.into_bytes())
|
||||
};
|
||||
}
|
||||
|
||||
@@ -378,6 +378,21 @@ macro_rules! read_heap_cell_pat_body {
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
});
|
||||
($cell:ident, CutPoint, $value:ident, $code:expr) => ({
|
||||
let $value = Fixnum::from_bytes($cell.into_bytes());
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
});
|
||||
($cell:ident, Fixnum | CutPoint, $value:ident, $code:expr) => ({
|
||||
let $value = Fixnum::from_bytes($cell.into_bytes());
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
});
|
||||
($cell:ident, CutPoint | Fixnum, $value:ident, $code:expr) => ({
|
||||
let $value = Fixnum::from_bytes($cell.into_bytes());
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
});
|
||||
($cell:ident, Char, $value:ident, $code:expr) => ({
|
||||
let $value = unsafe { char::from_u32_unchecked($cell.get_value() as u32) };
|
||||
#[allow(unused_braces)]
|
||||
@@ -540,23 +555,7 @@ macro_rules! functor_term {
|
||||
macro_rules! compare_number_instr {
|
||||
($cmp: expr, $at_1: expr, $at_2: expr) => {{
|
||||
$cmp.set_terms($at_1, $at_2);
|
||||
call_clause!(ClauseType::Inlined(InlinedClauseType::CompareNumber($cmp)), 0)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! call_clause {
|
||||
($clause_type:expr, $pvs:expr) => {{
|
||||
let mut instr = $clause_type.to_instr();
|
||||
instr.perm_vars_mut().map(|pvs| *pvs = $pvs);
|
||||
instr
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! call_clause_by_default {
|
||||
($clause_type:expr, $pvs:expr) => {{
|
||||
let mut instr = $clause_type.to_instr().to_default();
|
||||
instr.perm_vars_mut().map(|pvs| *pvs = $pvs);
|
||||
instr
|
||||
ClauseType::Inlined(InlinedClauseType::CompareNumber($cmp)).to_instr()
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -590,6 +589,7 @@ macro_rules! index_store {
|
||||
extensible_predicates: ExtensiblePredicates::with_hasher(FxBuildHasher::default()),
|
||||
local_extensible_predicates: LocalExtensiblePredicates::with_hasher(FxBuildHasher::default()),
|
||||
global_variables: GlobalVarDir::with_hasher(FxBuildHasher::default()),
|
||||
goal_expansion_indices: GoalExpansionIndices::with_hasher(FxBuildHasher::default()),
|
||||
meta_predicates: MetaPredicateDir::with_hasher(FxBuildHasher::default()),
|
||||
modules: $modules,
|
||||
op_dir: $op_dir,
|
||||
@@ -625,6 +625,12 @@ macro_rules! compare_term_test {
|
||||
$machine_st.pdl.push($e2);
|
||||
$machine_st.pdl.push($e1);
|
||||
|
||||
$machine_st.compare_term_test()
|
||||
$machine_st.compare_term_test(VarComparison::Distinct)
|
||||
}};
|
||||
($machine_st:expr, $e1:expr, $e2:expr, $var_comparison:expr) => {{
|
||||
$machine_st.pdl.push($e2);
|
||||
$machine_st.pdl.push($e1);
|
||||
|
||||
$machine_st.compare_term_test($var_comparison)
|
||||
}};
|
||||
}
|
||||
|
||||
+218
-11
@@ -4,15 +4,15 @@ use crate::machine::machine_indices::*;
|
||||
use crate::parser::char_reader::*;
|
||||
use crate::types::HeapCellValueTag;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::io::{Error as IOError};
|
||||
use std::ops::Neg;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::{Error as IOError, ErrorKind};
|
||||
use std::ops::{Deref, Neg};
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexMap;
|
||||
@@ -227,7 +227,7 @@ macro_rules! perm_v {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum GenContext {
|
||||
Head,
|
||||
Mid(usize),
|
||||
@@ -303,17 +303,19 @@ pub type OpDir = IndexMap<(Atom, Fixity), OpDesc, FxBuildHasher>;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MachineFlags {
|
||||
pub double_quotes: DoubleQuotes,
|
||||
pub unknown: Unknown,
|
||||
}
|
||||
|
||||
impl Default for MachineFlags {
|
||||
fn default() -> Self {
|
||||
MachineFlags {
|
||||
double_quotes: DoubleQuotes::default(),
|
||||
unknown: Unknown::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum DoubleQuotes {
|
||||
Atom,
|
||||
Chars,
|
||||
@@ -340,6 +342,34 @@ impl Default for DoubleQuotes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Unknown {
|
||||
Error,
|
||||
Fail,
|
||||
Warn,
|
||||
}
|
||||
|
||||
impl Unknown {
|
||||
pub fn is_error(self) -> bool {
|
||||
matches!(self, Unknown::Error)
|
||||
}
|
||||
|
||||
pub fn is_fail(self) -> bool {
|
||||
matches!(self, Unknown::Fail)
|
||||
}
|
||||
|
||||
pub fn is_warn(self) -> bool {
|
||||
matches!(self, Unknown::Warn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Unknown {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Unknown::Error
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_op_dir() -> OpDir {
|
||||
let mut op_dir = OpDir::with_hasher(FxBuildHasher::default());
|
||||
|
||||
@@ -380,7 +410,7 @@ pub enum ParserError {
|
||||
NonPrologChar(usize, usize),
|
||||
ParseBigInt(usize, usize),
|
||||
UnexpectedChar(char, usize, usize),
|
||||
UnexpectedEOF,
|
||||
// UnexpectedEOF,
|
||||
Utf8Error(usize, usize),
|
||||
}
|
||||
|
||||
@@ -403,16 +433,30 @@ impl ParserError {
|
||||
ParserError::BackQuotedString(..) => atom!("back_quoted_string"),
|
||||
ParserError::IncompleteReduction(..) => atom!("incomplete_reduction"),
|
||||
ParserError::InvalidSingleQuotedCharacter(..) => atom!("invalid_single_quoted_character"),
|
||||
ParserError::IO(e) if e.kind() == ErrorKind::UnexpectedEof => atom!("unexpected_end_of_file"),
|
||||
ParserError::IO(_) => atom!("input_output_error"),
|
||||
ParserError::LexicalError(_) => atom!("lexical_error"), // TODO: ?
|
||||
ParserError::LexicalError(_) => atom!("lexical_error"),
|
||||
ParserError::MissingQuote(..) => atom!("missing_quote"),
|
||||
ParserError::NonPrologChar(..) => atom!("non_prolog_character"),
|
||||
ParserError::ParseBigInt(..) => atom!("cannot_parse_big_int"),
|
||||
ParserError::UnexpectedChar(..) => atom!("unexpected_char"),
|
||||
ParserError::UnexpectedEOF => atom!("unexpected_end_of_file"),
|
||||
ParserError::Utf8Error(..) => atom!("utf8_conversion_error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unexpected_eof() -> Self {
|
||||
ParserError::IO(std::io::Error::from(ErrorKind::UnexpectedEof))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_unexpected_eof(&self) -> bool {
|
||||
if let ParserError::IO(e) = self {
|
||||
e.kind() == ErrorKind::UnexpectedEof
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lexical::Error> for ParserError {
|
||||
@@ -493,6 +537,21 @@ impl Fixnum {
|
||||
.with_f(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_cutpoint(num: i64) -> Self {
|
||||
Fixnum::new()
|
||||
.with_num(u64::from_ne_bytes(num.to_ne_bytes()) & ((1 << 56) - 1))
|
||||
.with_tag(HeapCellValueTag::CutPoint as u8)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_tag(&self) -> HeapCellValueTag {
|
||||
use modular_bitfield::Specifier;
|
||||
HeapCellValueTag::from_bytes(self.tag()).unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build_with_checked(num: i64) -> Result<Self, OutOfBounds> {
|
||||
const UPPER_BOUND: i64 = (1 << 55) - 1;
|
||||
@@ -572,6 +631,110 @@ impl Literal {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VarPtr(Rc<RefCell<Var>>);
|
||||
|
||||
impl Hash for VarPtr {
|
||||
#[inline(always)]
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.borrow().hash(hasher)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for VarPtr {
|
||||
type Target = RefCell<Var>;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl VarPtr {
|
||||
#[inline(always)]
|
||||
pub(crate) fn borrow(&self) -> Ref<'_, Var> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn borrow_mut(&self) -> RefMut<'_, Var> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn to_var_num(&self) -> Option<usize> {
|
||||
match *self.borrow() {
|
||||
Var::Generated(var_num) => Some(var_num),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set(&self, var: Var) {
|
||||
let mut var_ref = self.borrow_mut();
|
||||
*var_ref = var;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Var> for VarPtr {
|
||||
#[inline(always)]
|
||||
fn from(value: Var) -> VarPtr {
|
||||
VarPtr(Rc::new(RefCell::new(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for VarPtr {
|
||||
#[inline(always)]
|
||||
fn from(value: String) -> VarPtr {
|
||||
VarPtr::from(Var::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for VarPtr {
|
||||
#[inline(always)]
|
||||
fn from(value: &str) -> VarPtr {
|
||||
VarPtr::from(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Var {
|
||||
Generated(usize),
|
||||
InSitu(usize),
|
||||
Named(String),
|
||||
}
|
||||
|
||||
impl From<String> for Var {
|
||||
#[inline(always)]
|
||||
fn from(value: String) -> Var {
|
||||
Var::Named(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Var {
|
||||
#[inline(always)]
|
||||
fn from(value: &str) -> Var {
|
||||
Var::Named(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl Var {
|
||||
#[inline(always)]
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match self {
|
||||
Var::Named(value) => Some(&value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
Var::InSitu(n) | Var::Generated(n) => format!("_{}", n),
|
||||
Var::Named(value) => value.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Term {
|
||||
AnonVar,
|
||||
@@ -582,7 +745,7 @@ pub enum Term {
|
||||
// other PartialString variants in as_partial_string.
|
||||
PartialString(Cell<RegType>, String, Box<Term>),
|
||||
CompleteString(Cell<RegType>, Atom),
|
||||
Var(Cell<VarReg>, Rc<String>),
|
||||
Var(Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
impl Term {
|
||||
@@ -626,8 +789,25 @@ impl Term {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source_arity(terms: &[Term]) -> usize {
|
||||
if let Some(last_arg) = terms.last() {
|
||||
if let Term::Literal(_, Literal::CodeIndex(_)) = last_arg {
|
||||
return terms.len() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
terms.len()
|
||||
}
|
||||
|
||||
fn unfold_by_str_once(term: &mut Term, s: Atom) -> Option<(Term, Term)> {
|
||||
if let Term::Clause(_, ref name, ref mut subterms) = term {
|
||||
if let Some(last_arg) = subterms.last() {
|
||||
if let Term::Literal(_, Literal::CodeIndex(_)) = last_arg {
|
||||
subterms.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if name == &s && subterms.len() == 2 {
|
||||
let snd = subterms.pop().unwrap();
|
||||
let fst = subterms.pop().unwrap();
|
||||
@@ -650,3 +830,30 @@ pub fn unfold_by_str(mut term: Term, s: Atom) -> Vec<Term> {
|
||||
terms.push(term);
|
||||
terms
|
||||
}
|
||||
|
||||
fn unfold_by_str_ref_once(term: &Term, s: Atom) -> Option<(&Term, &Term)> {
|
||||
if let Term::Clause(_, ref name, ref subterms) = term {
|
||||
if name == &s && subterms.len() == 2 {
|
||||
let fst = &subterms[0];
|
||||
let snd = &subterms[1];
|
||||
|
||||
return Some((fst, snd));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unfold_by_str_ref(mut term: &Term, s: Atom) -> Vec<&Term> {
|
||||
let mut terms = vec![];
|
||||
|
||||
while let Some((fst, snd)) = unfold_by_str_ref_once(&term, s) {
|
||||
terms.push(fst);
|
||||
term = snd;
|
||||
}
|
||||
|
||||
terms.push(term);
|
||||
terms
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::str;
|
||||
|
||||
pub struct CharReader<R> {
|
||||
inner: R,
|
||||
buf: SmallVec<[u8;4]>,
|
||||
buf: SmallVec<[u8;32]>,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
@@ -111,17 +111,15 @@ impl<R> CharReader<R> {
|
||||
}
|
||||
|
||||
impl<R: Read> CharReader<R> {
|
||||
fn refresh_buffer(&mut self) -> io::Result<&[u8]> {
|
||||
pub fn refresh_buffer(&mut self) -> io::Result<&[u8]> {
|
||||
// If we've reached the end of our internal buffer then we need to fetch
|
||||
// some more data from the underlying reader.
|
||||
// Branch using `>=` instead of the more correct `==`
|
||||
// to tell the compiler that the pos..cap slice is always valid.
|
||||
if self.pos >= self.buf.len() {
|
||||
debug_assert!(self.pos == self.buf.len());
|
||||
|
||||
self.buf.clear();
|
||||
|
||||
let mut word = [0u8;4];
|
||||
let mut word = [0u8; std::mem::size_of::<char>()];
|
||||
let nread = self.inner.read(&mut word)?;
|
||||
|
||||
self.buf.extend_from_slice(&word[..nread]);
|
||||
@@ -130,6 +128,19 @@ impl<R: Read> CharReader<R> {
|
||||
|
||||
Ok(&self.buf[self.pos..])
|
||||
}
|
||||
|
||||
pub fn peek_byte(&mut self) -> Option<io::Result<u8>> {
|
||||
match self.refresh_buffer() {
|
||||
Ok(_buf) => {}
|
||||
Err(e) => return Some(Err(e)),
|
||||
}
|
||||
|
||||
return if let Some(b) = self.buf.get(0).cloned() {
|
||||
Some(Ok(b))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> CharRead for CharReader<R> {
|
||||
@@ -187,7 +198,7 @@ impl<R: Read> CharRead for CharReader<R> {
|
||||
if self.pos >= self.buf.len() {
|
||||
return None;
|
||||
} else if self.buf.len() - self.pos >= 4 {
|
||||
return match str::from_utf8(&self.buf[..e.valid_up_to()]) {
|
||||
return match str::from_utf8(&self.buf[self.pos .. e.valid_up_to()]) {
|
||||
Ok(s) => {
|
||||
let mut chars = s.chars();
|
||||
let c = chars.next().unwrap();
|
||||
@@ -195,7 +206,7 @@ impl<R: Read> CharRead for CharReader<R> {
|
||||
Some(Ok(c))
|
||||
}
|
||||
Err(e) => {
|
||||
let badbytes = self.buf[..e.valid_up_to()].to_vec();
|
||||
let badbytes = self.buf[self.pos .. e.valid_up_to()].to_vec();
|
||||
|
||||
Some(Err(io::Error::new(io::ErrorKind::InvalidData,
|
||||
BadUtf8Error { bytes: badbytes })))
|
||||
@@ -234,10 +245,10 @@ impl<R: Read> CharRead for CharReader<R> {
|
||||
#[inline(always)]
|
||||
fn put_back_char(&mut self, c: char) {
|
||||
let src_len = self.buf.len() - self.pos;
|
||||
debug_assert!(src_len <= 4);
|
||||
debug_assert!(src_len <= self.buf.capacity());
|
||||
|
||||
let c_len = c.len_utf8();
|
||||
let mut shifted_slice = [0u8; 4];
|
||||
let mut shifted_slice = [0u8; 32];
|
||||
|
||||
shifted_slice[0..src_len].copy_from_slice(&self.buf[self.pos .. self.buf.len()]);
|
||||
|
||||
|
||||
+87
-67
@@ -5,25 +5,11 @@ use crate::atom_table::*;
|
||||
pub use crate::machine::machine_state::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::char_reader::*;
|
||||
use crate::parser::rug::Integer;
|
||||
use crate::parser::dashu::Integer;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! is_not_eof {
|
||||
($parser:expr, $c:expr) => {
|
||||
match $c {
|
||||
Ok('\u{0}') => {
|
||||
$parser.consume('\u{0}'.len_utf8());
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(c) => c,
|
||||
Err($crate::parser::ast::ParserError::UnexpectedEOF) => return Ok(true),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! consume_chars_with {
|
||||
($token:expr, $e:expr) => {
|
||||
loop {
|
||||
@@ -37,6 +23,12 @@ macro_rules! consume_chars_with {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct LayoutInfo {
|
||||
inserted: bool,
|
||||
more: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Token {
|
||||
Literal(Literal),
|
||||
@@ -94,14 +86,14 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
pub fn lookahead_char(&mut self) -> Result<char, ParserError> {
|
||||
match self.reader.peek_char() {
|
||||
Some(Ok(c)) => Ok(c),
|
||||
_ => Err(ParserError::UnexpectedEOF)
|
||||
_ => Err(ParserError::unexpected_eof())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_char(&mut self) -> Result<char, ParserError> {
|
||||
match self.reader.read_char() {
|
||||
Some(Ok(c)) => Ok(c),
|
||||
_ => Err(ParserError::UnexpectedEOF)
|
||||
_ => Err(ParserError::unexpected_eof())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +102,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
self.reader.put_back_char(c);
|
||||
}
|
||||
|
||||
fn skip_char(&mut self, c: char) {
|
||||
pub fn skip_char(&mut self, c: char) {
|
||||
self.reader.consume(c.len_utf8());
|
||||
|
||||
if new_line_char!(c) {
|
||||
@@ -121,18 +113,6 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eof(&mut self) -> Result<bool, ParserError> {
|
||||
let mut c = is_not_eof!(self.reader, self.lookahead_char());
|
||||
|
||||
while layout_char!(c) {
|
||||
self.skip_char(c);
|
||||
|
||||
c = is_not_eof!(self.reader, self.lookahead_char());
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn single_line_comment(&mut self) -> Result<(), ParserError> {
|
||||
loop {
|
||||
if self.reader.peek_char().is_none() {
|
||||
@@ -168,17 +148,32 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
|
||||
let mut c = self.lookahead_char()?;
|
||||
|
||||
loop {
|
||||
while !comment_2_char!(c) {
|
||||
let mut comment_loop = || -> Result<(), ParserError> {
|
||||
loop {
|
||||
while !comment_2_char!(c) {
|
||||
self.skip_char(c);
|
||||
c = self.lookahead_char()?;
|
||||
}
|
||||
|
||||
self.skip_char(c);
|
||||
c = self.lookahead_char()?;
|
||||
|
||||
if comment_1_char!(c) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.skip_char(c);
|
||||
c = self.lookahead_char()?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if comment_1_char!(c) {
|
||||
break;
|
||||
match comment_loop() {
|
||||
Err(e) if e.is_unexpected_eof() => {
|
||||
return Err(ParserError::IncompleteReduction(self.line_num, self.col_num));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(_) => {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,7 +854,13 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
|
||||
self.get_single_quoted_char()
|
||||
.map(|c| Token::Literal(Literal::Fixnum(Fixnum::build_with(c as i64))))
|
||||
.or_else(|_| {
|
||||
.or_else(|err| {
|
||||
match err {
|
||||
ParserError::UnexpectedChar('\'', ..) => {
|
||||
}
|
||||
err => return Err(err),
|
||||
}
|
||||
|
||||
self.return_char(c);
|
||||
|
||||
i64::from_str_radix(&token, 10)
|
||||
@@ -908,38 +909,57 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan_for_layout(&mut self) -> Result<bool, ParserError> {
|
||||
let mut layout_inserted = false;
|
||||
let mut more_layout = true;
|
||||
|
||||
loop {
|
||||
let cr = self.lookahead_char();
|
||||
|
||||
match cr {
|
||||
Ok(c) if layout_char!(c) => {
|
||||
self.skip_char(c);
|
||||
layout_inserted = true;
|
||||
fn consume_layout(
|
||||
&mut self,
|
||||
c: Option<char>,
|
||||
layout_info: &mut LayoutInfo,
|
||||
) -> Result<(), ParserError> {
|
||||
match c {
|
||||
Some(c) if layout_char!(c) => {
|
||||
self.skip_char(c);
|
||||
layout_info.inserted = true;
|
||||
}
|
||||
Some(c) if end_line_comment_char!(c) => {
|
||||
self.single_line_comment()?;
|
||||
layout_info.inserted = true;
|
||||
}
|
||||
Some(c) if comment_1_char!(c) => {
|
||||
if self.bracketed_comment()? {
|
||||
layout_info.inserted = true;
|
||||
} else {
|
||||
layout_info.more = false;
|
||||
}
|
||||
Ok(c) if end_line_comment_char!(c) => {
|
||||
self.single_line_comment()?;
|
||||
layout_inserted = true;
|
||||
}
|
||||
Ok(c) if comment_1_char!(c) => {
|
||||
if self.bracketed_comment()? {
|
||||
layout_inserted = true;
|
||||
} else {
|
||||
more_layout = false;
|
||||
}
|
||||
}
|
||||
_ => more_layout = false,
|
||||
};
|
||||
|
||||
if !more_layout {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
layout_info.more = false;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(layout_inserted)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scan_for_layout(&mut self) -> Result<bool, ParserError> {
|
||||
match self.lookahead_char() {
|
||||
Err(e) => {
|
||||
Err(e)
|
||||
}
|
||||
Ok(c) => {
|
||||
let mut layout_info = LayoutInfo { inserted: false, more: true };
|
||||
let mut cr = Some(c);
|
||||
|
||||
loop {
|
||||
self.consume_layout(cr, &mut layout_info)?;
|
||||
|
||||
if !layout_info.more {
|
||||
break;
|
||||
}
|
||||
|
||||
cr = self.lookahead_char().ok();
|
||||
}
|
||||
|
||||
Ok(layout_info.inserted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_token(&mut self) -> Result<Token, ParserError> {
|
||||
@@ -982,7 +1002,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
|
||||
return Ok(Token::End);
|
||||
}
|
||||
Err(ParserError::UnexpectedEOF) => {
|
||||
Err(e) if e.is_unexpected_eof() => {
|
||||
return Ok(Token::End);
|
||||
}
|
||||
_ => {
|
||||
@@ -1034,7 +1054,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
|
||||
}
|
||||
|
||||
if c == '\u{0}' {
|
||||
return Err(ParserError::UnexpectedEOF);
|
||||
return Err(ParserError::unexpected_eof())
|
||||
}
|
||||
|
||||
self.name_token(c)
|
||||
|
||||
@@ -7,14 +7,20 @@ macro_rules! char_class {
|
||||
#[macro_export]
|
||||
macro_rules! alpha_char {
|
||||
($c: expr) => {
|
||||
$c.is_alphabetic() || $c == '_'
|
||||
(!$c.is_numeric() &&
|
||||
!$c.is_whitespace() &&
|
||||
!$c.is_control() &&
|
||||
!$crate::graphic_token_char!($c) &&
|
||||
!$crate::layout_char!($c) &&
|
||||
!$crate::meta_char!($c) &&
|
||||
!$crate::solo_char!($c)) || $c == '_'
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! alpha_numeric_char {
|
||||
($c: expr) => {
|
||||
$crate::alpha_char!($c) || $crate::decimal_digit_char!($c)
|
||||
$crate::alpha_char!($c) || $c.is_numeric()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
-5
@@ -1,8 +1,4 @@
|
||||
#[cfg(feature = "num-rug-adapter")]
|
||||
pub use num_rug_adapter as rug;
|
||||
|
||||
#[cfg(feature = "rug")]
|
||||
pub use rug;
|
||||
pub use dashu;
|
||||
|
||||
// #[macro_use]
|
||||
// extern crate lazy_static;
|
||||
|
||||
+55
-28
@@ -1,14 +1,16 @@
|
||||
use dashu::Integer;
|
||||
use dashu::Rational;
|
||||
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::char_reader::*;
|
||||
use crate::parser::lexer::*;
|
||||
|
||||
use crate::parser::rug::ops::NegAssign;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::ops::Neg;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum TokenType {
|
||||
@@ -25,6 +27,16 @@ enum TokenType {
|
||||
End,
|
||||
}
|
||||
|
||||
/*
|
||||
Specifies whether the token sequence should be read from the lexer or
|
||||
provided via the Provided variant.
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub enum Tokens {
|
||||
Default,
|
||||
Provided(Vec<Token>),
|
||||
}
|
||||
|
||||
impl TokenType {
|
||||
fn is_sep(self) -> bool {
|
||||
matches!(
|
||||
@@ -266,7 +278,7 @@ fn read_tokens<R: CharRead>(lexer: &mut Lexer<R>) -> Result<Vec<Token>, ParserEr
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ParserError::UnexpectedEOF) if !tokens.is_empty() => {
|
||||
Err(e) if e.is_unexpected_eof() && !tokens.is_empty() => {
|
||||
return Err(ParserError::IncompleteReduction(
|
||||
lexer.line_num,
|
||||
lexer.col_num,
|
||||
@@ -303,8 +315,17 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
Parser {
|
||||
lexer: Lexer::new(stream, machine_st),
|
||||
tokens: vec![],
|
||||
stack: Vec::new(),
|
||||
terms: Vec::new(),
|
||||
stack: vec![],
|
||||
terms: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_lexer(lexer: Lexer<'a, R>) -> Self {
|
||||
Parser {
|
||||
lexer,
|
||||
tokens: vec![],
|
||||
stack: vec![],
|
||||
terms: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +448,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
if v.trim() == "_" {
|
||||
self.terms.push(Term::AnonVar);
|
||||
} else {
|
||||
self.terms.push(Term::Var(Cell::default(), Rc::new(v)));
|
||||
self.terms.push(Term::Var(Cell::default(), VarPtr::from(v)));
|
||||
}
|
||||
|
||||
TokenType::Term
|
||||
@@ -602,11 +623,6 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn devour_whitespace(&mut self) -> Result<(), ParserError> {
|
||||
self.lexer.scan_for_layout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.stack.clear()
|
||||
}
|
||||
@@ -828,6 +844,10 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(TokenType::Open | TokenType::OpenCT) = self.stack.last().map(|token| token.tt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let idx = self.stack.len() - 2;
|
||||
let td = self.stack.remove(idx);
|
||||
|
||||
@@ -861,7 +881,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
}) = get_op_desc(name, op_dir)
|
||||
{
|
||||
if (pre > 0 && inf + post > 0) || is_negate!(spec) {
|
||||
match self.tokens.last().ok_or(ParserError::UnexpectedEOF)? {
|
||||
match self.tokens.last().ok_or(ParserError::unexpected_eof())? {
|
||||
// do this when layout hasn't been inserted,
|
||||
// ie. why we don't match on Token::Open.
|
||||
Token::OpenCT => {
|
||||
@@ -906,7 +926,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
|
||||
fn negate_number<N, Negator, ToLiteral>(&mut self, n: N, negator: Negator, constr: ToLiteral)
|
||||
where
|
||||
Negator: Fn(N) -> N,
|
||||
Negator: Fn(N, &mut Arena) -> N,
|
||||
ToLiteral: Fn(N, &mut Arena) -> Literal,
|
||||
{
|
||||
if let Some(desc) = self.stack.last().cloned() {
|
||||
@@ -918,7 +938,9 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
self.stack.pop();
|
||||
self.terms.pop();
|
||||
|
||||
let literal = constr(negator(n), &mut self.lexer.machine_st.arena);
|
||||
let arena = &mut self.lexer.machine_st.arena;
|
||||
let literal = constr(negator(n, arena), arena);
|
||||
|
||||
self.shift(Token::Literal(literal), 0, TERM);
|
||||
|
||||
return;
|
||||
@@ -933,24 +955,31 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
}
|
||||
|
||||
fn shift_token(&mut self, token: Token, op_dir: &CompositeOpDir) -> Result<(), ParserError> {
|
||||
fn negate_rc<T: NegAssign>(mut t: TypedArenaPtr<T>) -> TypedArenaPtr<T> {
|
||||
(&mut t).neg_assign();
|
||||
t
|
||||
fn negate_int_rc(t: TypedArenaPtr<Integer>, arena: &mut Arena) -> TypedArenaPtr<Integer> {
|
||||
let i: Integer = (*t).clone();
|
||||
let data = i.neg();
|
||||
arena_alloc!(data, arena)
|
||||
}
|
||||
|
||||
fn negate_rat_rc(t: TypedArenaPtr<Rational>, arena: &mut Arena) -> TypedArenaPtr<Rational> {
|
||||
let r: Rational = (*t).clone();
|
||||
let data = r.neg();
|
||||
arena_alloc!(data, arena)
|
||||
}
|
||||
|
||||
match token {
|
||||
Token::Literal(Literal::Fixnum(n)) => {
|
||||
self.negate_number(n, |n| -n, |n, _| Literal::Fixnum(n))
|
||||
self.negate_number(n, |n, _| -n, |n, _| Literal::Fixnum(n))
|
||||
}
|
||||
Token::Literal(Literal::Integer(n)) => {
|
||||
self.negate_number(n, negate_rc, |n, _| Literal::Integer(n))
|
||||
self.negate_number(n, negate_int_rc, |n, _| Literal::Integer(n))
|
||||
}
|
||||
Token::Literal(Literal::Rational(n)) => {
|
||||
self.negate_number(n, negate_rc, |r, _| Literal::Rational(r))
|
||||
self.negate_number(n, negate_rat_rc, |r, _| Literal::Rational(r))
|
||||
}
|
||||
Token::Literal(Literal::Float(n)) => self.negate_number(
|
||||
**n.as_ptr(),
|
||||
|n| -n,
|
||||
|n, _| -n,
|
||||
|n, arena| Literal::from(float_alloc!(n, arena)),
|
||||
),
|
||||
Token::Literal(c) => {
|
||||
@@ -1029,11 +1058,6 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn eof(&mut self) -> Result<bool, ParserError> {
|
||||
self.lexer.eof()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_lines_read(&mut self, lines_read: usize) {
|
||||
self.lexer.line_num += lines_read;
|
||||
@@ -1045,8 +1069,11 @@ impl<'a, R: CharRead> Parser<'a, R> {
|
||||
}
|
||||
|
||||
// on success, returns the parsed term and the number of lines read.
|
||||
pub fn read_term(&mut self, op_dir: &CompositeOpDir) -> Result<Term, ParserError> {
|
||||
self.tokens = read_tokens(&mut self.lexer)?;
|
||||
pub fn read_term(&mut self, op_dir: &CompositeOpDir, tokens: Tokens) -> Result<Term, ParserError> {
|
||||
self.tokens = match tokens {
|
||||
Tokens::Default => read_tokens(&mut self.lexer)?,
|
||||
Tokens::Provided(tokens) => tokens,
|
||||
};
|
||||
|
||||
while let Some(token) = self.tokens.pop() {
|
||||
self.shift_token(token, op_dir)?;
|
||||
|
||||
+87
-67
@@ -17,6 +17,7 @@ use fxhash::FxBuildHasher;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::history::DefaultHistory;
|
||||
use rustyline::{Config, Editor};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
@@ -24,19 +25,38 @@ use std::io::{Cursor, Error, ErrorKind, Read};
|
||||
|
||||
type SubtermDeque = VecDeque<(usize, usize)>;
|
||||
|
||||
impl MachineState {
|
||||
pub(crate) fn devour_whitespace(
|
||||
&mut self,
|
||||
mut inner: Stream,
|
||||
) -> Result<bool, ParserError> {
|
||||
let mut parser = Parser::new(inner, self);
|
||||
pub(crate) fn devour_whitespace<'a, R: CharRead>(parser: &mut Parser<'a, R>) -> Result<bool, ParserError> {
|
||||
match parser.lexer.scan_for_layout() {
|
||||
Err(e) if e.is_unexpected_eof() => {
|
||||
Ok(true)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
Ok(_) => {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parser.devour_whitespace()?;
|
||||
inner.add_lines_read(parser.lines_read());
|
||||
pub(crate) fn error_after_read_term<R>(
|
||||
err: ParserError,
|
||||
prior_num_lines_read: usize,
|
||||
parser: &Parser<R>,
|
||||
) -> CompilationError {
|
||||
if err.is_unexpected_eof() {
|
||||
let line_num = parser.lexer.line_num;
|
||||
let col_num = parser.lexer.col_num;
|
||||
|
||||
parser.eof()
|
||||
// rough overlap with errors 8.14.1.3 k) & l) of the ISO standard here
|
||||
if !(line_num == prior_num_lines_read && col_num == 0) {
|
||||
return CompilationError::from(ParserError::IncompleteReduction(line_num, col_num));
|
||||
}
|
||||
}
|
||||
|
||||
CompilationError::from(err)
|
||||
}
|
||||
|
||||
|
||||
impl MachineState {
|
||||
pub(crate) fn read(
|
||||
&mut self,
|
||||
mut inner: Stream,
|
||||
@@ -45,11 +65,12 @@ impl MachineState {
|
||||
let (term, num_lines_read) = {
|
||||
let prior_num_lines_read = inner.lines_read();
|
||||
let mut parser = Parser::new(inner, self);
|
||||
let op_dir = CompositeOpDir::new(op_dir, None);
|
||||
|
||||
parser.add_lines_read(prior_num_lines_read);
|
||||
|
||||
let term = parser.read_term(&CompositeOpDir::new(op_dir, None))
|
||||
.map_err(CompilationError::from)?;
|
||||
let term = parser.read_term(&op_dir, Tokens::Default)
|
||||
.map_err(|err| error_after_read_term(err, prior_num_lines_read, &parser))?; // CompilationError::from
|
||||
|
||||
(term, parser.lines_read() - prior_num_lines_read)
|
||||
};
|
||||
@@ -60,7 +81,6 @@ impl MachineState {
|
||||
}
|
||||
|
||||
static mut PROMPT: bool = false;
|
||||
|
||||
const HISTORY_FILE: &'static str = ".scryer_history";
|
||||
|
||||
pub(crate) fn set_prompt(value: bool) {
|
||||
@@ -82,18 +102,21 @@ fn get_prompt() -> &'static str {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReadlineStream {
|
||||
rl: Editor<Helper>,
|
||||
pending_input: Cursor<String>,
|
||||
rl: Editor<Helper, DefaultHistory>,
|
||||
pending_input: CharReader<Cursor<String>>,
|
||||
add_history: bool,
|
||||
}
|
||||
|
||||
impl ReadlineStream {
|
||||
#[inline]
|
||||
pub fn new(pending_input: &str, add_history: bool) -> Self {
|
||||
let config = Config::builder().check_cursor_position(true).build();
|
||||
let config = Config::builder()
|
||||
.check_cursor_position(true)
|
||||
.build();
|
||||
|
||||
let helper = Helper::new();
|
||||
|
||||
let mut rl = Editor::with_config(config);
|
||||
let mut rl = Editor::with_config(config).unwrap();
|
||||
rl.set_helper(Some(helper));
|
||||
|
||||
if let Some(mut path) = dirs_next::home_dir() {
|
||||
@@ -103,11 +126,9 @@ impl ReadlineStream {
|
||||
}
|
||||
}
|
||||
|
||||
// rl.bind_sequence(KeyEvent::from('\t'), Cmd::Insert(1, "\t".to_string()));
|
||||
|
||||
ReadlineStream {
|
||||
rl,
|
||||
pending_input: Cursor::new(pending_input.to_owned()),
|
||||
pending_input: CharReader::new(Cursor::new(pending_input.to_owned())),
|
||||
add_history: add_history,
|
||||
}
|
||||
}
|
||||
@@ -119,31 +140,37 @@ impl ReadlineStream {
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.pending_input.get_mut().clear();
|
||||
self.pending_input.set_position(0);
|
||||
self.pending_input.reset_buffer();
|
||||
|
||||
let pending_input = self.pending_input.get_mut();
|
||||
|
||||
pending_input.get_mut().clear();
|
||||
pending_input.set_position(0);
|
||||
}
|
||||
|
||||
fn call_readline(&mut self) -> std::io::Result<usize> {
|
||||
match self.rl.readline(get_prompt()) {
|
||||
Ok(text) => {
|
||||
*self.pending_input.get_mut() = text;
|
||||
self.pending_input.set_position(0);
|
||||
self.pending_input.reset_buffer();
|
||||
|
||||
*self.pending_input.get_mut().get_mut() = text;
|
||||
self.pending_input.get_mut().set_position(0);
|
||||
|
||||
unsafe {
|
||||
if PROMPT {
|
||||
self.rl.history_mut().add(self.pending_input.get_ref());
|
||||
self.rl.add_history_entry(self.pending_input.get_ref().get_ref()).unwrap();
|
||||
self.save_history();
|
||||
PROMPT = false;
|
||||
}
|
||||
|
||||
if self.pending_input.get_ref().get_ref().chars().last() != Some('\n') {
|
||||
*self.pending_input.get_mut().get_mut() += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if self.pending_input.get_ref().chars().last() != Some('\n') {
|
||||
*self.pending_input.get_mut() += "\n";
|
||||
}
|
||||
|
||||
Ok(self.pending_input.get_ref().len())
|
||||
Ok(self.pending_input.get_ref().get_ref().len())
|
||||
}
|
||||
Err(ReadlineError::Eof) => Ok(0),
|
||||
Err(ReadlineError::Eof) => Err(Error::from(ErrorKind::UnexpectedEof)),
|
||||
Err(e) => Err(Error::new(ErrorKind::InvalidInput, e)),
|
||||
}
|
||||
}
|
||||
@@ -164,12 +191,13 @@ impl ReadlineStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn peek_byte(&mut self) -> std::io::Result<u8> {
|
||||
let bytes = self.pending_input.refresh_buffer()?;
|
||||
let byte = bytes.iter().next().cloned();
|
||||
|
||||
loop {
|
||||
match self.pending_input.get_ref().bytes().next() {
|
||||
Some(0) => {
|
||||
return Ok(0);
|
||||
}
|
||||
match byte {
|
||||
Some(b) => {
|
||||
return Ok(b);
|
||||
}
|
||||
@@ -177,10 +205,6 @@ impl ReadlineStream {
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(0) => {
|
||||
self.pending_input.get_mut().push('\u{0}');
|
||||
return Ok(0);
|
||||
}
|
||||
_ => {
|
||||
set_prompt(false);
|
||||
}
|
||||
@@ -203,26 +227,18 @@ impl Read for ReadlineStream {
|
||||
}
|
||||
|
||||
impl CharRead for ReadlineStream {
|
||||
#[inline]
|
||||
fn peek_char(&mut self) -> Option<std::io::Result<char>> {
|
||||
loop {
|
||||
let pos = self.pending_input.position() as usize;
|
||||
|
||||
match self.pending_input.get_ref()[pos ..].chars().next() {
|
||||
Some('\u{0}') => {
|
||||
return Some(Ok('\u{0}'));
|
||||
}
|
||||
Some(c) => {
|
||||
match self.pending_input.peek_char() {
|
||||
Some(Ok(c)) => {
|
||||
return Some(Ok(c));
|
||||
}
|
||||
None => {
|
||||
_ => {
|
||||
match self.call_readline() {
|
||||
Err(e) => {
|
||||
return Some(Err(e));
|
||||
}
|
||||
Ok(0) => {
|
||||
self.pending_input.get_mut().push('\u{0}');
|
||||
return Some(Ok('\u{0}'));
|
||||
}
|
||||
_ => {
|
||||
set_prompt(false);
|
||||
}
|
||||
@@ -232,21 +248,21 @@ impl CharRead for ReadlineStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn consume(&mut self, nread: usize) {
|
||||
let offset = self.pending_input.position() as usize;
|
||||
self.pending_input.set_position((offset + nread) as u64);
|
||||
self.pending_input.consume(nread);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn put_back_char(&mut self, c: char) {
|
||||
let offset = self.pending_input.position() as usize;
|
||||
self.pending_input.set_position((offset - c.len_utf8()) as u64);
|
||||
self.pending_input.put_back_char(c);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn write_term_to_heap(
|
||||
term: &Term,
|
||||
heap: &mut Heap,
|
||||
pub(crate) fn write_term_to_heap<'a, 'b>(
|
||||
term: &'a Term,
|
||||
heap: &'b mut Heap,
|
||||
atom_tbl: &mut AtomTable,
|
||||
) -> Result<TermWriteResult, CompilationError> {
|
||||
let term_writer = TermWriter::new(heap, atom_tbl);
|
||||
@@ -279,7 +295,7 @@ impl<'a, 'b> TermWriter<'a, 'b> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn modify_head_of_queue(&mut self, term: &TermRef<'a>, h: usize) {
|
||||
fn modify_head_of_queue(&mut self, term: &TermRef, h: usize) {
|
||||
if let Some((arity, site_h)) = self.queue.pop_front() {
|
||||
self.heap[site_h] = self.term_as_addr(term, h);
|
||||
|
||||
@@ -295,7 +311,7 @@ impl<'a, 'b> TermWriter<'a, 'b> {
|
||||
self.heap.push(heap_loc_as_cell!(h));
|
||||
}
|
||||
|
||||
fn term_as_addr(&mut self, term: &TermRef<'a>, h: usize) -> HeapCellValue {
|
||||
fn term_as_addr(&mut self, term: &TermRef, h: usize) -> HeapCellValue {
|
||||
match term {
|
||||
&TermRef::Cons(..) => list_loc_as_cell!(h),
|
||||
&TermRef::AnonVar(_) | &TermRef::Var(..) => heap_loc_as_cell!(h),
|
||||
@@ -314,10 +330,10 @@ impl<'a, 'b> TermWriter<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_term_to_heap(mut self, term: &'a Term) -> Result<TermWriteResult, CompilationError> {
|
||||
fn write_term_to_heap(mut self, term: &Term) -> Result<TermWriteResult, CompilationError> {
|
||||
let heap_loc = self.heap.len();
|
||||
|
||||
for term in breadth_first_iter(term, true) {
|
||||
for term in breadth_first_iter(term, RootIterationPolicy::Iterated) {
|
||||
let h = self.heap.len();
|
||||
|
||||
match &term {
|
||||
@@ -368,17 +384,19 @@ impl<'a, 'b> TermWriter<'a, 'b> {
|
||||
self.push_stub_addr();
|
||||
}
|
||||
}
|
||||
&TermRef::AnonVar(Level::Root) | &TermRef::Literal(Level::Root, ..) => {
|
||||
&TermRef::AnonVar(Level::Root) | TermRef::Literal(Level::Root, ..) => {
|
||||
let addr = self.term_as_addr(&term, h);
|
||||
self.heap.push(addr);
|
||||
}
|
||||
&TermRef::Var(Level::Root, _, ref var) => {
|
||||
&TermRef::Var(Level::Root, _, ref var_ptr) => {
|
||||
let addr = self.term_as_addr(&term, h);
|
||||
self.var_dict.insert(var.clone(), heap_loc_as_cell!(h));
|
||||
self.var_dict.insert(VarKey::VarPtr(var_ptr.clone()), addr);
|
||||
self.heap.push(addr);
|
||||
}
|
||||
&TermRef::AnonVar(_) => {
|
||||
if let Some((arity, site_h)) = self.queue.pop_front() {
|
||||
self.var_dict.insert(VarKey::AnonVar(h), heap_loc_as_cell!(site_h));
|
||||
|
||||
if arity > 1 {
|
||||
self.queue.push_front((arity - 1, site_h + 1));
|
||||
}
|
||||
@@ -405,12 +423,14 @@ impl<'a, 'b> TermWriter<'a, 'b> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
&TermRef::Var(_, _, ref var) => {
|
||||
&TermRef::Var(.., ref var) => {
|
||||
if let Some((arity, site_h)) = self.queue.pop_front() {
|
||||
if let Some(addr) = self.var_dict.get(var).cloned() {
|
||||
let var_key = VarKey::VarPtr(var.clone());
|
||||
|
||||
if let Some(addr) = self.var_dict.get(&var_key).cloned() {
|
||||
self.heap[site_h] = addr;
|
||||
} else {
|
||||
self.var_dict.insert(var.clone(), heap_loc_as_cell!(site_h));
|
||||
self.var_dict.insert(var_key, heap_loc_as_cell!(site_h));
|
||||
}
|
||||
|
||||
if arity > 1 {
|
||||
|
||||
+26
-5
@@ -16,7 +16,7 @@ pub(crate) trait CompilationTarget<'a> {
|
||||
|
||||
fn to_constant(lvl: Level, literal: Literal, r: RegType) -> Instruction;
|
||||
fn to_list(lvl: Level, r: RegType) -> Instruction;
|
||||
fn to_structure(name: Atom, arity: usize, r: RegType) -> Instruction;
|
||||
fn to_structure(lvl: Level, name: Atom, arity: usize, r: RegType) -> Instruction;
|
||||
|
||||
fn to_void(num_subterms: usize) -> Instruction;
|
||||
fn is_void_instr(instr: &Instruction) -> bool;
|
||||
@@ -29,11 +29,13 @@ pub(crate) trait CompilationTarget<'a> {
|
||||
|
||||
fn argument_to_variable(r: RegType, r: usize) -> Instruction;
|
||||
fn argument_to_value(r: RegType, val: usize) -> Instruction;
|
||||
fn unsafe_argument_to_value(r: RegType, val: usize) -> Instruction;
|
||||
|
||||
fn move_to_register(r: RegType, val: usize) -> Instruction;
|
||||
|
||||
fn subterm_to_variable(r: RegType) -> Instruction;
|
||||
fn subterm_to_value(r: RegType) -> Instruction;
|
||||
fn unsafe_subterm_to_value(r: RegType) -> Instruction;
|
||||
|
||||
fn clause_arg_to_instr(r: RegType) -> Instruction;
|
||||
}
|
||||
@@ -42,15 +44,15 @@ impl<'a> CompilationTarget<'a> for FactInstruction {
|
||||
type Iterator = FactIterator<'a>;
|
||||
|
||||
fn iter(term: &'a Term) -> Self::Iterator {
|
||||
breadth_first_iter(term, false) // do not iterate over the root clause if one exists.
|
||||
breadth_first_iter(term, RootIterationPolicy::NotIterated)
|
||||
}
|
||||
|
||||
fn to_constant(lvl: Level, constant: Literal, reg: RegType) -> Instruction {
|
||||
Instruction::GetConstant(lvl, HeapCellValue::from(constant), reg)
|
||||
}
|
||||
|
||||
fn to_structure(name: Atom, arity: usize, reg: RegType) -> Instruction {
|
||||
Instruction::GetStructure(name, arity, reg)
|
||||
fn to_structure(lvl: Level, name: Atom, arity: usize, reg: RegType) -> Instruction {
|
||||
Instruction::GetStructure(lvl, name, arity, reg)
|
||||
}
|
||||
|
||||
fn to_list(lvl: Level, reg: RegType) -> Instruction {
|
||||
@@ -95,6 +97,10 @@ impl<'a> CompilationTarget<'a> for FactInstruction {
|
||||
Instruction::GetValue(arg, val)
|
||||
}
|
||||
|
||||
fn unsafe_argument_to_value(arg: RegType, val: usize) -> Instruction {
|
||||
Instruction::GetValue(arg, val)
|
||||
}
|
||||
|
||||
fn subterm_to_variable(val: RegType) -> Instruction {
|
||||
Instruction::UnifyVariable(val)
|
||||
}
|
||||
@@ -103,6 +109,10 @@ impl<'a> CompilationTarget<'a> for FactInstruction {
|
||||
Instruction::UnifyValue(val)
|
||||
}
|
||||
|
||||
fn unsafe_subterm_to_value(val: RegType) -> Instruction {
|
||||
Instruction::UnifyLocalValue(val)
|
||||
}
|
||||
|
||||
fn clause_arg_to_instr(val: RegType) -> Instruction {
|
||||
Instruction::UnifyVariable(val)
|
||||
}
|
||||
@@ -115,7 +125,7 @@ impl<'a> CompilationTarget<'a> for QueryInstruction {
|
||||
post_order_iter(term)
|
||||
}
|
||||
|
||||
fn to_structure(name: Atom, arity: usize, r: RegType) -> Instruction {
|
||||
fn to_structure(_lvl: Level, name: Atom, arity: usize, r: RegType) -> Instruction {
|
||||
Instruction::PutStructure(name, arity, r)
|
||||
}
|
||||
|
||||
@@ -165,6 +175,13 @@ impl<'a> CompilationTarget<'a> for QueryInstruction {
|
||||
Instruction::PutValue(arg, val)
|
||||
}
|
||||
|
||||
fn unsafe_argument_to_value(arg: RegType, val: usize) -> Instruction {
|
||||
match arg {
|
||||
RegType::Perm(p) => Instruction::PutUnsafeValue(p, val),
|
||||
RegType::Temp(_) => Instruction::PutValue(arg, val),
|
||||
}
|
||||
}
|
||||
|
||||
fn subterm_to_variable(val: RegType) -> Instruction {
|
||||
Instruction::SetVariable(val)
|
||||
}
|
||||
@@ -173,6 +190,10 @@ impl<'a> CompilationTarget<'a> for QueryInstruction {
|
||||
Instruction::SetValue(val)
|
||||
}
|
||||
|
||||
fn unsafe_subterm_to_value(val: RegType) -> Instruction {
|
||||
Instruction::SetLocalValue(val)
|
||||
}
|
||||
|
||||
fn clause_arg_to_instr(val: RegType) -> Instruction {
|
||||
Instruction::SetValue(val)
|
||||
}
|
||||
|
||||
+54
-37
@@ -1,6 +1,7 @@
|
||||
:- module('$toplevel', [argv/1,
|
||||
copy_term/3]).
|
||||
|
||||
:- use_module(library(atts), [call_residue_vars/2]).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(files)).
|
||||
@@ -82,7 +83,7 @@ print_help :-
|
||||
|
||||
print_version :-
|
||||
'$scryer_prolog_version'(Version),
|
||||
write(Version), nl,
|
||||
maplist(put_char, Version), nl,
|
||||
halt.
|
||||
|
||||
gather_goal(Type, Args0, Goals) :-
|
||||
@@ -114,18 +115,27 @@ layout_and_dot([C|Cs]) :-
|
||||
layout_and_dot(Cs).
|
||||
|
||||
run_goals([]).
|
||||
run_goals([g(Gs0)|Goals]) :-
|
||||
run_goals([g(Gs0)|Goals]) :- !,
|
||||
( ends_with_dot(Gs0) -> Gs1 = Gs0
|
||||
; append(Gs0, ".", Gs1)
|
||||
),
|
||||
read_from_chars(Gs1, Goal),
|
||||
( catch(
|
||||
user:Goal,
|
||||
Exception,
|
||||
(write(Goal), write(' causes: '), write(Exception), nl) % halt?
|
||||
)
|
||||
; write('Warning: initialization failed for '),
|
||||
write(Gs0), nl
|
||||
double_quotes_option(DQ),
|
||||
catch(read_term_from_chars(Gs1, Goal, [variable_names(VNs)]),
|
||||
E,
|
||||
( write_term(Gs0, [double_quotes(DQ)]),
|
||||
write(' cannot be read: '), write(E), nl,
|
||||
halt
|
||||
)
|
||||
),
|
||||
( catch(user:Goal,
|
||||
Exception,
|
||||
( write_term(Goal, [variable_names(VNs),double_quotes(DQ)]),
|
||||
write(' causes: '),
|
||||
write_term(Exception, [double_quotes(DQ)]), nl % halt?
|
||||
)
|
||||
) -> true
|
||||
; write('Warning: initialization failed for: '),
|
||||
write_term(Goal, [variable_names(VNs),double_quotes(DQ)]), nl
|
||||
),
|
||||
run_goals(Goals).
|
||||
run_goals([Goal|_]) :-
|
||||
@@ -180,8 +190,9 @@ submit_query_and_print_results_(Term, VarList) :-
|
||||
'$get_b_value'(B),
|
||||
bb_put('$report_all', false),
|
||||
bb_put('$report_n_more', 0),
|
||||
call(user:Term),
|
||||
write_eqs_and_read_input(B, VarList),
|
||||
expand_goal(Term, user, Term0),
|
||||
atts:call_residue_vars(user:Term0, AttrVars),
|
||||
write_eqs_and_read_input(B, VarList, AttrVars),
|
||||
!.
|
||||
submit_query_and_print_results_(_, _) :-
|
||||
( bb_get('$answer_count', 0) ->
|
||||
@@ -203,22 +214,29 @@ submit_query_and_print_results(Term, VarList) :-
|
||||
|
||||
|
||||
needs_bracketing(Value, Op) :-
|
||||
catch((functor(Value, F, _),
|
||||
current_op(EqPrec, EqSpec, Op),
|
||||
current_op(FPrec, _, F)),
|
||||
_,
|
||||
false),
|
||||
( EqPrec < FPrec ->
|
||||
true
|
||||
; FPrec > 0, F == Value, graphic_token_char(F) ->
|
||||
true
|
||||
; F \== '.', '$quoted_token'(F) ->
|
||||
true
|
||||
; EqPrec == FPrec,
|
||||
memberchk(EqSpec, [fx,xfx,yfx])
|
||||
nonvar(Value),
|
||||
functor(Value, F, Arity),
|
||||
atom(F),
|
||||
current_op(FPrec, FSpec, F),
|
||||
current_op(EqPrec, EqSpec, Op),
|
||||
arity_specifier(Arity, FSpec),
|
||||
( Arity =:= 0
|
||||
; EqPrec < FPrec
|
||||
; EqPrec =:= FPrec,
|
||||
member(EqSpec, [fx,xfx,yfx])
|
||||
).
|
||||
|
||||
arity_specifier(0, _).
|
||||
arity_specifier(1, S) :- atom_chars(S, [_,_]).
|
||||
arity_specifier(2, S) :- atom_chars(S, [_,_,_]).
|
||||
|
||||
double_quotes_option(DQ) :-
|
||||
( current_prolog_flag(double_quotes, chars) -> DQ = true
|
||||
; DQ = false
|
||||
).
|
||||
|
||||
write_goal(G, VarList, MaxDepth) :-
|
||||
double_quotes_option(DQ),
|
||||
( G = (Var = Value) ->
|
||||
( var(Value) ->
|
||||
select((Var = _), VarList, NewVarList)
|
||||
@@ -226,18 +244,19 @@ write_goal(G, VarList, MaxDepth) :-
|
||||
),
|
||||
write(Var),
|
||||
write(' = '),
|
||||
( needs_bracketing(Value, (=)) ->
|
||||
( needs_bracketing(Value, =) ->
|
||||
write('('),
|
||||
write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth)]),
|
||||
write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth), double_quotes(DQ)]),
|
||||
write(')')
|
||||
; write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth)])
|
||||
; write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth), double_quotes(DQ)])
|
||||
)
|
||||
; G == [] ->
|
||||
write('true')
|
||||
; write_term(G, [quoted(true), variable_names(VarList), max_depth(MaxDepth)])
|
||||
; write_term(G, [quoted(true), variable_names(VarList), max_depth(MaxDepth), double_quotes(DQ)])
|
||||
).
|
||||
|
||||
write_last_goal(G, VarList, MaxDepth) :-
|
||||
double_quotes_option(DQ),
|
||||
( G = (Var = Value) ->
|
||||
( var(Value) ->
|
||||
select((Var = _), VarList, NewVarList)
|
||||
@@ -245,11 +264,11 @@ write_last_goal(G, VarList, MaxDepth) :-
|
||||
),
|
||||
write(Var),
|
||||
write(' = '),
|
||||
( needs_bracketing(Value, (=)) ->
|
||||
( needs_bracketing(Value, =) ->
|
||||
write('('),
|
||||
write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth)]),
|
||||
write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth), double_quotes(DQ)]),
|
||||
write(')')
|
||||
; write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth)]),
|
||||
; write_term(Value, [quoted(true), variable_names(NewVarList), max_depth(MaxDepth), double_quotes(DQ)]),
|
||||
( trailing_period_is_ambiguous(Value) ->
|
||||
write(' ')
|
||||
; true
|
||||
@@ -257,7 +276,7 @@ write_last_goal(G, VarList, MaxDepth) :-
|
||||
)
|
||||
; G == [] ->
|
||||
write('true')
|
||||
; write_term(G, [quoted(true), variable_names(VarList), max_depth(MaxDepth)])
|
||||
; write_term(G, [quoted(true), variable_names(VarList), max_depth(MaxDepth), double_quotes(DQ)])
|
||||
).
|
||||
|
||||
write_eq((G1, G2), VarList, MaxDepth) :-
|
||||
@@ -269,8 +288,7 @@ write_eq(G, VarList, MaxDepth) :-
|
||||
write_last_goal(G, VarList, MaxDepth).
|
||||
|
||||
graphic_token_char(C) :-
|
||||
memberchk(C, ['#', '$', '&', '*', '+', '-', '.', ('/'), ':',
|
||||
'<', '=', '>', '?', '@', '^', '~', ('\\')]).
|
||||
memberchk(C, [#, $, &, *, +, -, ., /, :, <, =, >, ?, @, ^, ~, \]).
|
||||
|
||||
list_last_item([C], C) :- !.
|
||||
list_last_item([_|Cs], D) :-
|
||||
@@ -286,11 +304,10 @@ trailing_period_is_ambiguous(Value) :-
|
||||
term_variables_under_max_depth(Term, MaxDepth, Vars) :-
|
||||
'$term_variables_under_max_depth'(Term, MaxDepth, Vars).
|
||||
|
||||
write_eqs_and_read_input(B, VarList) :-
|
||||
write_eqs_and_read_input(B, VarList, AttrVars) :-
|
||||
gather_query_vars(VarList, OrigVars),
|
||||
% one layer of depth added for (=/2) functor
|
||||
'$term_variables_under_max_depth'(OrigVars, 22, Vars0),
|
||||
'$term_attributed_variables'(VarList, AttrVars),
|
||||
'$project_atts':project_attributes(Vars0, AttrVars),
|
||||
copy_term(AttrVars, AttrVars, AttrGoals),
|
||||
term_variables(AttrGoals, AttrGoalVars),
|
||||
|
||||
+13
-13
@@ -30,6 +30,7 @@ pub enum HeapCellValueTag {
|
||||
Atom = 0b010111,
|
||||
PStr = 0b011001,
|
||||
CStr = 0b011011,
|
||||
CutPoint = 0b011111,
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -50,14 +51,15 @@ pub enum HeapCellValueView {
|
||||
Atom = 0b010111,
|
||||
PStr = 0b011001,
|
||||
CStr = 0b011011,
|
||||
CutPoint = 0b011111,
|
||||
// trail elements.
|
||||
TrailedHeapVar = 0b011101,
|
||||
TrailedStackVar = 0b011111,
|
||||
TrailedAttrVarHeapLink = 0b100001,
|
||||
TrailedHeapVar = 0b101111,
|
||||
TrailedStackVar = 0b101011,
|
||||
TrailedAttrVar = 0b100001,
|
||||
TrailedAttrVarListLink = 0b100011,
|
||||
TrailedAttachedValue = 0b100101,
|
||||
TrailedBlackboardEntry = 0b100111,
|
||||
TrailedBlackboardOffset = 0b101001,
|
||||
TrailedBlackboardOffset = 0b110011,
|
||||
}
|
||||
|
||||
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -182,7 +184,6 @@ impl Ref {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TrailRef {
|
||||
Ref(Ref),
|
||||
AttrVarHeapLink(usize),
|
||||
AttrVarListLink(usize, usize),
|
||||
BlackboardEntry(Atom),
|
||||
BlackboardOffset(Atom, HeapCellValue), // key atom, key value
|
||||
@@ -191,14 +192,13 @@ pub enum TrailRef {
|
||||
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[bits = 6]
|
||||
pub(crate) enum TrailEntryTag {
|
||||
TrailedHeapVar = 0b011110,
|
||||
TrailedStackVar = 0b011111,
|
||||
TrailedAttrVar = 0b101110,
|
||||
TrailedAttrVarHeapLink = 0b100010,
|
||||
TrailedAttrVarListLink = 0b100011,
|
||||
TrailedAttachedValue = 0b101010,
|
||||
TrailedBlackboardEntry = 0b100110,
|
||||
TrailedBlackboardOffset = 0b100111,
|
||||
TrailedHeapVar = 0b101111,
|
||||
TrailedStackVar = 0b101011,
|
||||
TrailedAttrVar = 0b100001,
|
||||
TrailedAttrVarListLink = 0b100011,
|
||||
TrailedAttachedValue = 0b100101,
|
||||
TrailedBlackboardEntry = 0b100111,
|
||||
TrailedBlackboardOffset = 0b110011,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user