Skip to main content

datalog_structural_model/ir/
term.rs

1//! Datalog [`Term`] enum and the triple-term nesting bound.
2
3use std::fmt;
4
5use super::atom::Atom;
6use super::symbols::{ConstantSymbol, VariableSymbol};
7
8/// Datalog term (`variable`, `constant`, `quoted-triple term`,
9/// or `SKOLEM(name, args...)` function call).
10///
11/// The `TripleTerm` variant lets rules name quoted-triple
12/// arguments per RDF 1.2 / RDF-star, encoded as a 2-arity
13/// [`Atom`] whose predicate is the triple's predicate IRI and
14/// whose `terms` slot holds `[subject, object]`. Nesting is
15/// bounded by [`MAX_TRIPLE_TERM_NESTING`] to keep unification
16/// cost finite — programs that exceed the limit are rejected at
17/// [`Program::validate`](super::program::Program::validate) time.
18///
19/// The `Skolem` variant (extension) is a deterministic fresh-IRI
20/// minting operator: given a label and a tuple of argument terms,
21/// the evaluator produces a stable
22/// `urn:example:skolem:<label>:<hash>` IRI that's content-addressed
23/// from the argument bindings. SKOLEM is only meaningful in head
24/// positions — body atoms that carry a `Skolem` term are rejected
25/// at validation time as
26/// [`super::error::ProgramValidationError::SkolemInBodyAtom`]
27/// because a SKOLEM term has no pattern semantics (it's a
28/// function, not a wildcard).
29#[derive(Clone, Debug, Eq, Hash, PartialEq)]
30pub enum Term {
31    /// Variable term.
32    Variable(VariableSymbol),
33    /// Constant term.
34    Constant(ConstantSymbol),
35    /// Quoted-triple term: an inner [`Atom`] used as a value in
36    /// the enclosing atom. Conceptually represents `<< s p o >>`
37    /// per RDF 1.2 N-Triples-star syntax.
38    TripleTerm(Box<Atom>),
39    /// `SKOLEM(name, arg_1, …, arg_n)` — a deterministic fresh-IRI
40    /// minting function (extension). Permitted in rule heads only;
41    /// arguments must be [`Self::Variable`] or [`Self::Constant`]
42    /// (no nested SKOLEM, no quoted-triple terms).
43    Skolem {
44        /// Label that namespaces the skolemized IRI — typically a
45        /// short ASCII tag like `"example-key"` so collisions across
46        /// independent rule sets stay scoped per author.
47        name: ConstantSymbol,
48        /// Argument terms whose ground values feed the
49        /// content-addressed hash. Must be variables or constants.
50        args: Vec<Term>,
51    },
52}
53
54impl Term {
55    #[must_use]
56    pub(super) fn variable_name(&self) -> Option<&VariableSymbol> {
57        match self {
58            Self::Variable(var) => Some(var),
59            Self::Constant(_) | Self::TripleTerm(_) | Self::Skolem { .. } => None,
60        }
61    }
62
63    /// Depth of the term's triple-term nesting. `0` for plain
64    /// variables, constants, and SKOLEM terms (a SKOLEM produces a
65    /// flat IRI so it doesn't extend the triple-term tree);
66    /// `1 + max_child_depth` for a `TripleTerm` whose inner atom
67    /// contains other terms.
68    #[must_use]
69    pub(super) fn nesting_depth(&self) -> usize {
70        match self {
71            Self::Variable(_) | Self::Constant(_) | Self::Skolem { .. } => 0,
72            Self::TripleTerm(atom) => 1 + atom.nesting_depth(),
73        }
74    }
75
76    /// Returns `true` when the term is a `SKOLEM(...)` function
77    /// call. Used by body-atom validation to reject SKOLEM terms
78    /// outside head positions.
79    #[must_use]
80    pub fn is_skolem(&self) -> bool {
81        matches!(self, Self::Skolem { .. })
82    }
83}
84
85/// Maximum allowed nesting depth for [`Term::TripleTerm`]
86/// variants. Programs whose triple-term nesting exceeds this
87/// limit are rejected at
88/// [`Program::validate`](super::program::Program::validate) time.
89///
90/// The bound exists because unification cost grows with nesting
91/// and because the W3C RDF-star CG has not specified a
92/// normative bound — see
93/// [rdf12-triple-term-reasoning.mdx](the RDF-star specification)
94/// for the rationale.
95pub const MAX_TRIPLE_TERM_NESTING: usize = 4;
96
97/// Display a [`Term`]: variables as `?<name>`, constants as their
98/// raw symbol, and triple terms as `<< s p o >>` per
99/// N-Triples-star syntax.
100impl fmt::Display for Term {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Self::Variable(var) => write!(f, "?{}", var.as_str()),
104            Self::Constant(c) => write!(f, "{}", c.as_str()),
105            Self::TripleTerm(atom) => {
106                // RDF-star convention: `<< s p o >>` for the
107                // 2-arity case (subject + object as the inner
108                // atom's `terms`, predicate as the inner atom's
109                // `predicate`). For other arities — which are
110                // valid as a Datalog encoding even though they
111                // are not standard RDF triples — fall back to
112                // `<< pred(t1, t2, …) >>` so the print still
113                // round-trips through a developer's eye.
114                if atom.terms.len() == 2 {
115                    write!(
116                        f,
117                        "<< {} {} {} >>",
118                        atom.terms[0],
119                        atom.predicate.as_str(),
120                        atom.terms[1],
121                    )
122                } else {
123                    write!(f, "<< {atom} >>")
124                }
125            }
126            Self::Skolem { name, args } => {
127                write!(f, "SKOLEM({}", name.as_str())?;
128                for arg in args {
129                    write!(f, ", {arg}")?;
130                }
131                write!(f, ")")
132            }
133        }
134    }
135}