Skip to main content

patch_prolog_shared/
term.rs

1//! Prolog term representation (compiler-side AST).
2//!
3//! Ported from patch-prolog's `term.rs`, minus the serde derives — the
4//! compiled artifact is native code + a static atom table, never a
5//! serialized term database.
6
7use crate::atom::AtomId;
8
9pub type VarId = u32;
10
11/// Key for first-argument indexing.
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum FirstArgKey {
14    Atom(AtomId),
15    Integer(i64),
16    Functor(AtomId, usize), // functor atom id + arity
17}
18
19/// Prolog term representation.
20#[derive(Debug, Clone, PartialEq)]
21pub enum Term {
22    Atom(AtomId),
23    Var(VarId),
24    Integer(i64),
25    Float(f64),
26    Compound { functor: AtomId, args: Vec<Term> },
27    List { head: Box<Term>, tail: Box<Term> },
28}
29
30impl Term {
31    /// Extract functor AtomId and arity for a term used as a goal/head.
32    /// - Atom: (atom_id, 0)
33    /// - Compound: (functor, len(args))
34    /// - Others: None
35    pub fn functor_arity(&self) -> Option<(AtomId, usize)> {
36        match self {
37            Term::Atom(id) => Some((*id, 0)),
38            Term::Compound { functor, args } => Some((*functor, args.len())),
39            _ => None,
40        }
41    }
42
43    /// Extract the first-argument indexing key from a term used as a clause head.
44    pub fn first_arg_key(&self) -> Option<FirstArgKey> {
45        let first = match self {
46            Term::Compound { args, .. } if !args.is_empty() => &args[0],
47            _ => return None,
48        };
49        match first {
50            Term::Atom(id) => Some(FirstArgKey::Atom(*id)),
51            Term::Integer(n) => Some(FirstArgKey::Integer(*n)),
52            Term::Compound { functor, args } => Some(FirstArgKey::Functor(*functor, args.len())),
53            _ => None, // Var, Float, List -> not indexable
54        }
55    }
56
57    /// Check if this term is a variable.
58    pub fn is_var(&self) -> bool {
59        matches!(self, Term::Var(_))
60    }
61}
62
63/// A Prolog clause: head :- body.
64/// For facts, body is empty.
65#[derive(Debug, Clone, PartialEq)]
66pub struct Clause {
67    pub head: Term,
68    pub body: Vec<Term>,
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_term_functor_arity() {
77        let atom = Term::Atom(0);
78        assert_eq!(atom.functor_arity(), Some((0, 0)));
79
80        let compound = Term::Compound {
81            functor: 1,
82            args: vec![Term::Atom(2), Term::Var(0)],
83        };
84        assert_eq!(compound.functor_arity(), Some((1, 2)));
85
86        let var = Term::Var(0);
87        assert_eq!(var.functor_arity(), None);
88
89        let int = Term::Integer(42);
90        assert_eq!(int.functor_arity(), None);
91    }
92
93    #[test]
94    fn test_first_arg_key() {
95        // Compound with atom first arg
96        let t = Term::Compound {
97            functor: 0,
98            args: vec![Term::Atom(1)],
99        };
100        assert_eq!(t.first_arg_key(), Some(FirstArgKey::Atom(1)));
101
102        // Compound with integer first arg
103        let t = Term::Compound {
104            functor: 0,
105            args: vec![Term::Integer(42)],
106        };
107        assert_eq!(t.first_arg_key(), Some(FirstArgKey::Integer(42)));
108
109        // Compound with variable first arg -> None (not indexable)
110        let t = Term::Compound {
111            functor: 0,
112            args: vec![Term::Var(0)],
113        };
114        assert_eq!(t.first_arg_key(), None);
115
116        // Atom (no args) -> None
117        let t = Term::Atom(0);
118        assert_eq!(t.first_arg_key(), None);
119    }
120
121    #[test]
122    fn test_clause_construction() {
123        let clause = Clause {
124            head: Term::Compound {
125                functor: 0,
126                args: vec![Term::Atom(1), Term::Var(0)],
127            },
128            body: vec![Term::Compound {
129                functor: 2,
130                args: vec![Term::Var(0)],
131            }],
132        };
133        assert_eq!(clause.body.len(), 1);
134        assert_eq!(clause.head.functor_arity(), Some((0, 2)));
135    }
136}