use std::cmp::Ordering;
use crate::atom::AtomTable;
use crate::ets::MatchArena;
use crate::native::ProcessContext;
use crate::term::binary_ref::BinaryRef;
use crate::term::boxed::{BigInt, Cons, Tuple};
use crate::term::{Term, compare};
#[derive(Clone, Debug)]
pub struct CompiledMatchSpec {
spec: MatchSpec,
}
#[derive(Clone, Debug)]
pub struct MatchSpec {
clauses: Vec<MatchClause>,
}
#[derive(Clone, Debug)]
struct MatchClause {
head: Vec<Pattern>,
guards: Vec<Guard>,
body: Vec<BodyExpr>,
max_variable: usize,
}
#[derive(Clone, Debug)]
enum Pattern {
Variable(usize),
DontCare,
Literal(Term),
Tuple(Vec<Pattern>),
List(Vec<Pattern>),
}
#[derive(Clone, Debug)]
enum Guard {
Compare {
op: CompareOp,
left: GuardExpr,
right: GuardExpr,
},
TypeTest {
test: TypeTest,
value: GuardExpr,
},
}
#[derive(Copy, Clone, Debug)]
enum CompareOp {
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
#[derive(Copy, Clone, Debug)]
enum TypeTest {
Atom,
Integer,
Binary,
Tuple,
List,
}
#[derive(Clone, Debug)]
enum GuardExpr {
Variable(usize),
Literal(Term),
}
#[derive(Clone, Debug)]
enum BodyExpr {
Variable(usize),
Literal(Term),
Tuple(Vec<BodyExpr>),
List(Vec<BodyExpr>),
ReturnObject,
ReturnBindings,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MatchSpecError {
BadSpec,
BadClause,
BadHead,
BadGuard,
BadBody,
BadList,
UnknownGuard,
}
pub(crate) trait TermAllocator {
fn alloc_tuple(&mut self, elements: &[Term]) -> Result<Term, Term>;
fn alloc_list(&mut self, elements: &[Term]) -> Result<Term, Term>;
}
struct ProcessAllocator<'context, 'process> {
context: &'context mut ProcessContext<'process>,
}
impl TermAllocator for ProcessAllocator<'_, '_> {
fn alloc_tuple(&mut self, elements: &[Term]) -> Result<Term, Term> {
self.context.alloc_tuple(elements)
}
fn alloc_list(&mut self, elements: &[Term]) -> Result<Term, Term> {
self.context.alloc_list(elements)
}
}
impl CompiledMatchSpec {
pub fn compile(spec: Term, atom_table: &AtomTable) -> Result<Self, MatchSpecError> {
Ok(Self {
spec: MatchSpec::parse(spec, atom_table)?,
})
}
pub fn eval_with_context(
&self,
tuple: Term,
context: &mut ProcessContext<'_>,
) -> Result<Option<Term>, Term> {
let atom_table = context
.atom_table_arc()
.ok_or_else(|| Term::atom(crate::atom::Atom::BADARG))?;
let mut allocator = ProcessAllocator { context };
self.eval_with_allocator(tuple, atom_table.as_ref(), &mut allocator)
}
pub fn eval_with_arena(
&self,
tuple: Term,
atom_table: &AtomTable,
arena: &mut MatchArena,
) -> Result<Option<Term>, Term> {
self.eval_with_allocator(tuple, atom_table, arena)
}
pub fn run(
&self,
tuple: Term,
atom_table: &AtomTable,
arena: &mut MatchArena,
) -> Result<Option<Term>, Term> {
self.eval_with_arena(tuple, atom_table, arena)
}
#[must_use]
pub fn eval(&self, tuple: Term) -> Option<Term> {
let atom_table = AtomTable::with_common_atoms();
let mut arena = MatchArena::new();
for clause in &self.spec.clauses {
let mut bindings = vec![None; clause.max_variable.saturating_add(1)];
if !clause.matches_head(tuple, &mut bindings) {
continue;
}
if !clause
.guards
.iter()
.all(|guard| guard.eval(&bindings, &atom_table))
{
continue;
}
if clause.body.last().is_some_and(BodyExpr::allocates_result) {
return None;
}
return clause.eval_body(tuple, &bindings, &mut arena).ok();
}
None
}
fn eval_with_allocator(
&self,
object: Term,
atom_table: &AtomTable,
allocator: &mut dyn TermAllocator,
) -> Result<Option<Term>, Term> {
for clause in &self.spec.clauses {
let mut bindings = vec![None; clause.max_variable.saturating_add(1)];
if !clause.matches_head(object, &mut bindings) {
continue;
}
if !clause
.guards
.iter()
.all(|guard| guard.eval(&bindings, atom_table))
{
continue;
}
return clause.eval_body(object, &bindings, allocator).map(Some);
}
Ok(None)
}
}
impl MatchSpec {
pub fn parse(spec: Term, atom_table: &AtomTable) -> Result<Self, MatchSpecError> {
let clause_terms = proper_list(spec)?;
if clause_terms.is_empty() {
return Err(MatchSpecError::BadSpec);
}
let mut clauses = Vec::with_capacity(clause_terms.len());
for clause_term in clause_terms {
let tuple = Tuple::new(clause_term).ok_or(MatchSpecError::BadClause)?;
if tuple.arity() != 3 {
return Err(MatchSpecError::BadClause);
}
let head_term = tuple.get(0).ok_or(MatchSpecError::BadClause)?;
let guards_term = tuple.get(1).ok_or(MatchSpecError::BadClause)?;
let body_term = tuple.get(2).ok_or(MatchSpecError::BadClause)?;
clauses.push(MatchClause::parse(
head_term,
guards_term,
body_term,
atom_table,
)?);
}
Ok(Self { clauses })
}
}
impl MatchClause {
fn parse(
head_term: Term,
guards_term: Term,
body_term: Term,
atom_table: &AtomTable,
) -> Result<Self, MatchSpecError> {
let head_tuple = Tuple::new(head_term).ok_or(MatchSpecError::BadHead)?;
let mut max_variable = 0;
let mut head = Vec::with_capacity(head_tuple.arity());
for index in 0..head_tuple.arity() {
let element = head_tuple.get(index).ok_or(MatchSpecError::BadHead)?;
head.push(parse_pattern(element, atom_table, &mut max_variable)?);
}
let guard_terms = proper_list(guards_term).map_err(|_| MatchSpecError::BadGuard)?;
let mut guards = Vec::with_capacity(guard_terms.len());
for guard_term in guard_terms {
guards.push(parse_guard(guard_term, atom_table, &mut max_variable)?);
}
let body_terms = proper_list(body_term).map_err(|_| MatchSpecError::BadBody)?;
if body_terms.is_empty() {
return Err(MatchSpecError::BadBody);
}
let mut body = Vec::with_capacity(body_terms.len());
for expr_term in body_terms {
body.push(parse_body_expr(expr_term, atom_table, &mut max_variable)?);
}
Ok(Self {
head,
guards,
body,
max_variable,
})
}
fn matches_head(&self, object: Term, bindings: &mut [Option<Term>]) -> bool {
let Some(tuple) = Tuple::new(object) else {
return false;
};
if tuple.arity() != self.head.len() {
return false;
}
for (index, pattern) in self.head.iter().enumerate() {
let Some(value) = tuple.get(index) else {
return false;
};
if !match_pattern(pattern, value, bindings) {
return false;
}
}
true
}
fn eval_body(
&self,
object: Term,
bindings: &[Option<Term>],
allocator: &mut dyn TermAllocator,
) -> Result<Term, Term> {
let mut result = Term::NIL;
for expr in &self.body {
result = expr.eval(object, bindings, allocator)?;
}
Ok(result)
}
}
impl Guard {
fn eval(&self, bindings: &[Option<Term>], atom_table: &AtomTable) -> bool {
match self {
Self::Compare { op, left, right } => {
let Some(left) = left.eval(bindings) else {
return false;
};
let Some(right) = right.eval(bindings) else {
return false;
};
match op {
CompareOp::Eq => compare::numeric_eq(left, right),
CompareOp::Ne => !compare::numeric_eq(left, right),
CompareOp::Lt => compare::cmp(left, right, atom_table) == Ordering::Less,
CompareOp::Gt => compare::cmp(left, right, atom_table) == Ordering::Greater,
CompareOp::Le => compare::cmp(left, right, atom_table) != Ordering::Greater,
CompareOp::Ge => compare::cmp(left, right, atom_table) != Ordering::Less,
}
}
Self::TypeTest { test, value } => {
let Some(value) = value.eval(bindings) else {
return false;
};
match test {
TypeTest::Atom => value.is_atom(),
TypeTest::Integer => value.is_small_int() || BigInt::new(value).is_some(),
TypeTest::Binary => BinaryRef::new(value).is_some(),
TypeTest::Tuple => Tuple::new(value).is_some(),
TypeTest::List => value.is_list() || value.is_nil(),
}
}
}
}
}
impl GuardExpr {
fn eval(&self, bindings: &[Option<Term>]) -> Option<Term> {
match self {
Self::Variable(index) => bindings.get(*index).copied().flatten(),
Self::Literal(term) => Some(*term),
}
}
}
impl BodyExpr {
fn eval(
&self,
object: Term,
bindings: &[Option<Term>],
allocator: &mut dyn TermAllocator,
) -> Result<Term, Term> {
match self {
Self::Variable(index) => bindings.get(*index).copied().flatten().ok_or_else(badarg),
Self::Literal(term) => Ok(*term),
Self::Tuple(elements) => {
let evaluated = eval_body_exprs(elements, object, bindings, allocator)?;
allocator.alloc_tuple(&evaluated)
}
Self::List(elements) => {
let evaluated = eval_body_exprs(elements, object, bindings, allocator)?;
allocator.alloc_list(&evaluated)
}
Self::ReturnObject => Ok(object),
Self::ReturnBindings => {
let mut values = Vec::new();
for value in bindings.iter().skip(1).copied().flatten() {
values.push(value);
}
allocator.alloc_list(&values)
}
}
}
fn allocates_result(&self) -> bool {
match self {
Self::Tuple(_) | Self::List(_) | Self::ReturnBindings => true,
Self::Variable(_) | Self::Literal(_) | Self::ReturnObject => false,
}
}
}
fn eval_body_exprs(
exprs: &[BodyExpr],
object: Term,
bindings: &[Option<Term>],
allocator: &mut dyn TermAllocator,
) -> Result<Vec<Term>, Term> {
let mut evaluated = Vec::with_capacity(exprs.len());
for expr in exprs {
evaluated.push(expr.eval(object, bindings, allocator)?);
}
Ok(evaluated)
}
fn badarg() -> Term {
Term::atom(crate::atom::Atom::BADARG)
}
fn match_pattern(pattern: &Pattern, value: Term, bindings: &mut [Option<Term>]) -> bool {
match pattern {
Pattern::Variable(index) => {
let Some(slot) = bindings.get_mut(*index) else {
return false;
};
match *slot {
Some(bound) => compare::exact_eq(bound, value),
None => {
*slot = Some(value);
true
}
}
}
Pattern::DontCare => true,
Pattern::Literal(term) => compare::exact_eq(*term, value),
Pattern::Tuple(elements) => {
let Some(tuple) = Tuple::new(value) else {
return false;
};
tuple.arity() == elements.len()
&& elements.iter().enumerate().all(|(index, element)| {
tuple
.get(index)
.is_some_and(|value| match_pattern(element, value, bindings))
})
}
Pattern::List(elements) => proper_list(value).is_ok_and(|values| {
values.len() == elements.len()
&& elements
.iter()
.zip(values)
.all(|(pattern, value)| match_pattern(pattern, value, bindings))
}),
}
}
fn parse_pattern(
term: Term,
atom_table: &AtomTable,
max_variable: &mut usize,
) -> Result<Pattern, MatchSpecError> {
match classify_atom(term, atom_table) {
AtomClass::Variable(index) => {
*max_variable = (*max_variable).max(index);
Ok(Pattern::Variable(index))
}
AtomClass::DontCare => Ok(Pattern::DontCare),
AtomClass::SpecialReturn => Ok(Pattern::Literal(term)),
AtomClass::Literal => {
if let Some(tuple) = Tuple::new(term) {
let mut elements = Vec::with_capacity(tuple.arity());
for index in 0..tuple.arity() {
elements.push(parse_pattern(
tuple.get(index).ok_or(MatchSpecError::BadHead)?,
atom_table,
max_variable,
)?);
}
Ok(Pattern::Tuple(elements))
} else if term.is_list() || term.is_nil() {
let values = proper_list(term).map_err(|_| MatchSpecError::BadHead)?;
let mut elements = Vec::with_capacity(values.len());
for value in values {
elements.push(parse_pattern(value, atom_table, max_variable)?);
}
Ok(Pattern::List(elements))
} else {
Ok(Pattern::Literal(term))
}
}
}
}
fn parse_guard(
term: Term,
atom_table: &AtomTable,
max_variable: &mut usize,
) -> Result<Guard, MatchSpecError> {
let tuple = Tuple::new(term).ok_or(MatchSpecError::BadGuard)?;
let op_term = tuple.get(0).ok_or(MatchSpecError::BadGuard)?;
let Some(op_name) = atom_name(op_term, atom_table) else {
return Err(MatchSpecError::BadGuard);
};
if let Some(op) = parse_compare_op(op_name) {
if tuple.arity() != 3 {
return Err(MatchSpecError::BadGuard);
}
return Ok(Guard::Compare {
op,
left: parse_guard_expr(
tuple.get(1).ok_or(MatchSpecError::BadGuard)?,
atom_table,
max_variable,
)?,
right: parse_guard_expr(
tuple.get(2).ok_or(MatchSpecError::BadGuard)?,
atom_table,
max_variable,
)?,
});
}
if let Some(test) = parse_type_test(op_name) {
if tuple.arity() != 2 {
return Err(MatchSpecError::BadGuard);
}
return Ok(Guard::TypeTest {
test,
value: parse_guard_expr(
tuple.get(1).ok_or(MatchSpecError::BadGuard)?,
atom_table,
max_variable,
)?,
});
}
Err(MatchSpecError::UnknownGuard)
}
fn parse_guard_expr(
term: Term,
atom_table: &AtomTable,
max_variable: &mut usize,
) -> Result<GuardExpr, MatchSpecError> {
match classify_atom(term, atom_table) {
AtomClass::Variable(index) => {
*max_variable = (*max_variable).max(index);
Ok(GuardExpr::Variable(index))
}
AtomClass::DontCare | AtomClass::SpecialReturn => Err(MatchSpecError::BadGuard),
AtomClass::Literal => Ok(GuardExpr::Literal(term)),
}
}
fn parse_body_expr(
term: Term,
atom_table: &AtomTable,
max_variable: &mut usize,
) -> Result<BodyExpr, MatchSpecError> {
match classify_atom(term, atom_table) {
AtomClass::Variable(index) => {
*max_variable = (*max_variable).max(index);
Ok(BodyExpr::Variable(index))
}
AtomClass::DontCare => Ok(BodyExpr::Literal(term)),
AtomClass::SpecialReturn => match atom_name(term, atom_table) {
Some("$_") => Ok(BodyExpr::ReturnObject),
Some("$$") => Ok(BodyExpr::ReturnBindings),
_ => Err(MatchSpecError::BadBody),
},
AtomClass::Literal => {
if let Some(tuple) = Tuple::new(term) {
let constructor = if tuple.arity() == 1 {
Tuple::new(tuple.get(0).ok_or(MatchSpecError::BadBody)?)
} else {
Some(tuple)
};
let tuple = constructor.ok_or(MatchSpecError::BadBody)?;
let mut elements = Vec::with_capacity(tuple.arity());
for index in 0..tuple.arity() {
elements.push(parse_body_expr(
tuple.get(index).ok_or(MatchSpecError::BadBody)?,
atom_table,
max_variable,
)?);
}
Ok(BodyExpr::Tuple(elements))
} else if term.is_list() || term.is_nil() {
let values = proper_list(term).map_err(|_| MatchSpecError::BadBody)?;
let mut elements = Vec::with_capacity(values.len());
for value in values {
elements.push(parse_body_expr(value, atom_table, max_variable)?);
}
Ok(BodyExpr::List(elements))
} else {
Ok(BodyExpr::Literal(term))
}
}
}
}
fn parse_compare_op(name: &str) -> Option<CompareOp> {
match name {
"==" => Some(CompareOp::Eq),
"/=" => Some(CompareOp::Ne),
"<" => Some(CompareOp::Lt),
">" => Some(CompareOp::Gt),
"=<" => Some(CompareOp::Le),
">=" => Some(CompareOp::Ge),
_ => None,
}
}
fn parse_type_test(name: &str) -> Option<TypeTest> {
match name {
"is_atom" => Some(TypeTest::Atom),
"is_integer" => Some(TypeTest::Integer),
"is_binary" => Some(TypeTest::Binary),
"is_tuple" => Some(TypeTest::Tuple),
"is_list" => Some(TypeTest::List),
_ => None,
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum AtomClass {
Variable(usize),
DontCare,
SpecialReturn,
Literal,
}
fn classify_atom(term: Term, atom_table: &AtomTable) -> AtomClass {
let Some(name) = atom_name(term, atom_table) else {
return AtomClass::Literal;
};
if name == "_" {
AtomClass::DontCare
} else if name == "$_" || name == "$$" {
AtomClass::SpecialReturn
} else if let Some(index) = parse_variable_name(name) {
AtomClass::Variable(index)
} else {
AtomClass::Literal
}
}
fn parse_variable_name(name: &str) -> Option<usize> {
let digits = name.strip_prefix('$')?;
if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
let index = digits.parse::<usize>().ok()?;
(index > 0).then_some(index)
}
fn atom_name(term: Term, atom_table: &AtomTable) -> Option<&str> {
atom_table.resolve(term.as_atom()?)
}
fn proper_list(term: Term) -> Result<Vec<Term>, MatchSpecError> {
let mut elements = Vec::new();
let mut tail = term;
while !tail.is_nil() {
let cons = Cons::new(tail).ok_or(MatchSpecError::BadList)?;
elements.push(cons.head());
tail = cons.tail();
}
Ok(elements)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::atom::Atom;
use crate::native::ProcessContext;
use crate::process::Process;
use crate::term::boxed::{self, Tuple};
struct TestHeap {
tuple_words: Vec<Box<[u64]>>,
cons_words: Vec<Box<[u64]>>,
}
impl TestHeap {
fn new() -> Self {
Self {
tuple_words: Vec::new(),
cons_words: Vec::new(),
}
}
fn tuple(&mut self, elements: &[Term]) -> Term {
let mut words = vec![0_u64; 1 + elements.len()].into_boxed_slice();
let term = boxed::write_tuple(&mut words, elements).expect("test tuple fits");
self.tuple_words.push(words);
term
}
fn list(&mut self, elements: &[Term]) -> Term {
let mut tail = Term::NIL;
for element in elements.iter().rev().copied() {
let mut words = vec![0_u64; 2].into_boxed_slice();
tail = boxed::write_cons(&mut words, element, tail).expect("test cons fits");
self.cons_words.push(words);
}
tail
}
fn dotted_list(&mut self, head: Term, tail: Term) -> Term {
let mut words = vec![0_u64; 2].into_boxed_slice();
let term = boxed::write_cons(&mut words, head, tail).expect("test cons fits");
self.cons_words.push(words);
term
}
fn bigint(&mut self, negative: bool, limbs: &[u64]) -> Term {
let mut words = vec![0_u64; 3 + limbs.len()].into_boxed_slice();
let term = boxed::write_bigint(&mut words, negative, limbs).expect("test bigint fits");
self.tuple_words.push(words);
term
}
}
fn atom(table: &AtomTable, name: &str) -> Term {
Term::atom(table.intern(name))
}
fn compile_parse_acceptance(heap: &mut TestHeap, table: &AtomTable) -> CompiledMatchSpec {
let head = heap.tuple(&[atom(table, "$1"), atom(table, "$2")]);
let guard = heap.tuple(&[atom(table, ">"), atom(table, "$1"), Term::small_int(10)]);
let guards = heap.list(&[guard]);
let body = heap.list(&[atom(table, "$2")]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
CompiledMatchSpec::compile(spec, table).expect("spec compiles")
}
#[test]
fn parses_and_evaluates_acceptance_example() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let compiled = compile_parse_acceptance(&mut heap, &table);
let object = heap.tuple(&[Term::small_int(11), atom(&table, "ok")]);
assert_eq!(compiled.eval(object), Some(atom(&table, "ok")));
}
#[test]
fn guard_failure_returns_no_match() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$1"), atom(&table, "$2")]);
let guard = heap.tuple(&[atom(&table, ">"), atom(&table, "$1"), Term::small_int(0)]);
let guards = heap.list(&[guard]);
let body_tuple = heap.tuple(&[atom(&table, "$2"), atom(&table, "$1")]);
let body_constructor = heap.tuple(&[body_tuple]);
let body = heap.list(&[body_constructor]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
let compiled = CompiledMatchSpec::compile(spec, &table).expect("spec compiles");
let object = heap.tuple(&[Term::small_int(-1), atom(&table, "hello")]);
assert_eq!(compiled.eval(object), None);
}
#[test]
fn body_tuple_constructs_result() {
let table: Arc<AtomTable> = Arc::new(AtomTable::with_common_atoms());
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$1"), atom(&table, "$2")]);
let guard = heap.tuple(&[atom(&table, ">"), atom(&table, "$1"), Term::small_int(0)]);
let guards = heap.list(&[guard]);
let body_tuple = heap.tuple(&[atom(&table, "$2"), atom(&table, "$1")]);
let body_constructor = heap.tuple(&[body_tuple]);
let body = heap.list(&[body_constructor]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
let compiled = CompiledMatchSpec::compile(spec, &table).expect("spec compiles");
let object = heap.tuple(&[Term::small_int(5), atom(&table, "hello")]);
let mut process = Process::new(1, 233);
let mut context = ProcessContext::new();
context.attach_process(&mut process, 0);
context.set_atom_table(Some(Arc::clone(&table)));
let result = compiled
.eval_with_context(object, &mut context)
.expect("evaluation does not raise")
.expect("object matches");
let tuple = Tuple::new(result).expect("body result is tuple");
assert_eq!(tuple.get(0), Some(atom(&table, "hello")));
assert_eq!(tuple.get(1), Some(Term::small_int(5)));
}
#[test]
fn return_object_special_body_returns_whole_match() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$1"), atom(&table, "_")]);
let guards = heap.list(&[]);
let body = heap.list(&[atom(&table, "$_")]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
let compiled = CompiledMatchSpec::compile(spec, &table).expect("spec compiles");
let object = heap.tuple(&[Term::small_int(1), atom(&table, "ignored")]);
assert_eq!(compiled.eval(object), Some(object));
}
#[test]
fn return_bindings_special_body_returns_variables_by_index() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$2"), atom(&table, "$1")]);
let guards = heap.list(&[]);
let body = heap.list(&[atom(&table, "$$")]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
let compiled = CompiledMatchSpec::compile(spec, &table).expect("spec compiles");
let object = heap.tuple(&[atom(&table, "two"), atom(&table, "one")]);
let mut arena = MatchArena::new();
let result = compiled
.eval_with_arena(object, &table, &mut arena)
.expect("evaluation does not raise")
.expect("object matches");
let values = proper_list(result).expect("result is proper list");
assert_eq!(values, vec![atom(&table, "one"), atom(&table, "two")]);
}
#[test]
fn repeated_variable_requires_exact_equality() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$1"), atom(&table, "$1")]);
let guards = heap.list(&[]);
let body = heap.list(&[atom(&table, "$1")]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
let compiled = CompiledMatchSpec::compile(spec, &table).expect("spec compiles");
let matching = heap.tuple(&[Term::small_int(3), Term::small_int(3)]);
let non_matching = heap.tuple(&[Term::small_int(3), Term::small_int(4)]);
assert_eq!(compiled.eval(matching), Some(Term::small_int(3)));
assert_eq!(compiled.eval(non_matching), None);
}
#[test]
fn is_integer_type_test_accepts_bigints() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$1")]);
let guard = heap.tuple(&[atom(&table, "is_integer"), atom(&table, "$1")]);
let guards = heap.list(&[guard]);
let body = heap.list(&[atom(&table, "$1")]);
let clause = heap.tuple(&[head, guards, body]);
let spec = heap.list(&[clause]);
let compiled = CompiledMatchSpec::compile(spec, &table).expect("spec compiles");
let bigint = heap.bigint(false, &[u64::MAX, 1]);
let object = heap.tuple(&[bigint]);
assert_eq!(compiled.eval(object), Some(bigint));
}
#[test]
fn malformed_lists_are_rejected() {
let table = AtomTable::with_common_atoms();
let mut heap = TestHeap::new();
let head = heap.tuple(&[atom(&table, "$1")]);
let bad_guards = heap.dotted_list(Term::small_int(1), Term::atom(Atom::OK));
let body = heap.list(&[atom(&table, "$1")]);
let clause = heap.tuple(&[head, bad_guards, body]);
let spec = heap.list(&[clause]);
assert_eq!(
CompiledMatchSpec::compile(spec, &table).err(),
Some(MatchSpecError::BadGuard)
);
}
}