1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Prolog predicates.
//!
//! A prolog predicate is a core datatype in prolog. It is a
//! combination of a functor and a module. Just like functors and
//! modules, predicates are not reference-counted and are never
//! garbage collected.
//!
//! This module provides functors and types for intearcting with
//! prolog predicates.
use std::convert::TryInto;

use super::atom::*;
use super::engine::*;
use super::fli::*;
use super::functor::*;
use super::module::*;

/// A wrapper for a prolog predicate.
#[derive(Clone, Copy)]
pub struct Predicate {
    predicate: predicate_t,
}

// a `predicate_t` is a pointer which is normally not send or
// sync. SWI-Prolog however guarantees that this pointer will remain
// valid.
unsafe impl Send for Predicate {}
unsafe impl Sync for Predicate {}

impl Predicate {
    /// Wrap a `predicate_t`, which is how the SWI-Prolog fli represents predicates.
    ///
    /// # Safety
    /// This is unsafe because no check is done to ensure that the
    /// predicate_t indeed points at a valid predicate. The caller
    /// will have to ensure that this is the case.
    pub unsafe fn wrap(predicate: predicate_t) -> Self {
        Self { predicate }
    }

    /// Create a new predicate from the given functor and module.
    ///
    /// This will panic if no prolog engine is active on this thread.
    pub fn new(functor: Functor, module: Module) -> Self {
        assert_some_engine_is_active();
        let predicate = unsafe { PL_pred(functor.functor_ptr(), module.module_ptr()) };

        unsafe { Self::wrap(predicate) }
    }

    /// Return the underlying `predicate_t` which SWI-Prolog uses to refer to the predicate.
    pub fn predicate_ptr(&self) -> predicate_t {
        self.predicate
    }

    /// Retrieve the name of this predicate as an atom and pass it into the given function.
    ///
    /// The atom does not outlive this call, and the reference count
    /// is never incremented. This may be slightly faster in some
    /// cases than returning the name directly.
    ///
    /// This will panic if no prolog engine is active on this thread.
    pub fn with_name<F, R>(&self, func: F) -> R
    where
        F: Fn(&Atom) -> R,
    {
        assert_some_engine_is_active();
        let mut atom: atom_t = 0;
        unsafe {
            PL_predicate_info(
                self.predicate,
                &mut atom,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
            );
        }
        let atom = unsafe { Atom::wrap(atom) };

        let result = func(&atom);

        std::mem::forget(atom);

        result
    }

    /// Retrieve the name of this predicate as an atom.
    ///
    /// This will panic if no prolog engine is active on this thread.
    pub fn name(&self) -> Atom {
        self.with_name(|n| n.clone())
    }

    /// Retrieve the name of this predicate as a string.
    ///
    /// This will panic if no prolog engine is active on this thread.
    pub fn name_string(&self) -> String {
        self.with_name(|n| n.name())
    }

    /// Retrieve the arity of this predicate.
    ///
    /// This will panic if no prolog engine is active on this thread.
    pub fn arity(&self) -> u16 {
        assert_some_engine_is_active();
        let mut arity = 0;
        unsafe {
            PL_predicate_info(
                self.predicate,
                std::ptr::null_mut(),
                &mut arity,
                std::ptr::null_mut(),
            );
        }

        arity.try_into().unwrap()
    }

    /// Retrieve the module of this predicate.
    ///
    /// this will panic if no prolog engine is active on this thread.
    pub fn module(&self) -> Module {
        assert_some_engine_is_active();
        let mut module: module_t = std::ptr::null_mut();
        unsafe {
            PL_predicate_info(
                self.predicate,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                &mut module,
            );

            Module::wrap(module)
        }
    }
}