Skip to main content

sim_lib_logic/
db.rs

1//! Clause database: the store of facts and rules a query resolves against.
2//!
3//! [`LogicDb`] owns the clauses and maintains predicate and first-argument
4//! indexes so the resolver can narrow candidates quickly. It is plain runtime
5//! state, not a kernel contract; see the [`README`](https://docs.rs/sim-runtime).
6
7use std::collections::BTreeMap;
8
9use sim_kernel::{Expr, Result, Symbol};
10
11use crate::clause::{
12    Clause, ClauseId, goal_arity, goal_first_arg, normalize_goal_expr, parse_clause_expr,
13    predicate_symbol,
14};
15
16/// An indexed store of logic [`Clause`]s queried by goal.
17///
18/// Clauses are kept in assertion order and indexed by predicate symbol and by
19/// first ground argument to speed candidate lookup.
20#[derive(Clone, Debug, Default)]
21pub struct LogicDb {
22    clauses: Vec<Clause>,
23    by_predicate: BTreeMap<Symbol, Vec<ClauseId>>,
24    by_first_arg: BTreeMap<(Symbol, usize, String), Vec<ClauseId>>,
25}
26
27impl LogicDb {
28    /// Creates an empty clause database.
29    ///
30    /// # Examples
31    ///
32    /// ```
33    /// use sim_kernel::{Expr, Symbol};
34    /// use sim_lib_logic::LogicDb;
35    ///
36    /// let mut db = LogicDb::new();
37    /// db.assert_clause_expr(Expr::List(vec![
38    ///     Expr::Symbol(Symbol::new("fact")),
39    ///     Expr::List(vec![
40    ///         Expr::Symbol(Symbol::new("parent")),
41    ///         Expr::Symbol(Symbol::new("alice")),
42    ///         Expr::Symbol(Symbol::new("bob")),
43    ///     ]),
44    /// ]))
45    /// .unwrap();
46    /// assert!(db.predicate_exists(&Symbol::new("parent")));
47    /// assert_eq!(db.clauses().len(), 1);
48    /// ```
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    /// Returns all stored clauses in assertion order.
54    pub fn clauses(&self) -> &[Clause] {
55        &self.clauses
56    }
57
58    /// Returns whether any clause defines the given predicate symbol.
59    pub fn predicate_exists(&self, predicate: &Symbol) -> bool {
60        self.by_predicate
61            .get(predicate)
62            .is_some_and(|clauses| !clauses.is_empty())
63    }
64
65    /// Parses a clause expression and asserts it, returning its new id.
66    pub fn assert_clause_expr(&mut self, expr: Expr) -> Result<ClauseId> {
67        let id = ClauseId(self.clauses.len() + 1);
68        let clause = parse_clause_expr(id, expr)?;
69        self.assert_clause(clause)
70    }
71
72    /// Adds a parsed clause to the database and updates the indexes.
73    pub fn assert_clause(&mut self, clause: Clause) -> Result<ClauseId> {
74        let id = clause.id;
75        let predicate = clause.predicate()?;
76        let arity = clause.arity()?;
77        let first_key = goal_first_arg(&clause.head)
78            .filter(|expr| !matches!(expr, Expr::Local(_)))
79            .map(canonical_goal_key);
80        self.by_predicate
81            .entry(predicate.clone())
82            .or_default()
83            .push(id);
84        if let Some(first_key) = first_key {
85            self.by_first_arg
86                .entry((predicate, arity, first_key))
87                .or_default()
88                .push(id);
89        }
90        self.clauses.push(clause);
91        Ok(id)
92    }
93
94    /// Removes the first clause matching `expr`, rebuilding the indexes.
95    ///
96    /// Returns whether a matching clause was found and removed.
97    pub fn retract_clause_expr(&mut self, expr: &Expr) -> Result<bool> {
98        let target = parse_clause_expr(ClauseId(0), expr.clone())?;
99        if let Some(index) = self
100            .clauses
101            .iter()
102            .position(|clause| clause.fact_expr().canonical_eq(&target.fact_expr()))
103        {
104            self.clauses.remove(index);
105            self.rebuild_indexes()?;
106            return Ok(true);
107        }
108        Ok(false)
109    }
110
111    /// Returns the surface expressions of every fact for the given predicate.
112    pub fn facts(&self, predicate: &Symbol) -> Vec<Expr> {
113        self.clauses
114            .iter()
115            .filter(|clause| clause.body.is_empty())
116            .filter_map(|clause| match clause.predicate() {
117                Ok(symbol) if &symbol == predicate => Some(clause.fact_expr()),
118                _ => None,
119            })
120            .collect()
121    }
122
123    /// Looks up a clause by its identifier.
124    pub fn clause_by_id(&self, id: ClauseId) -> Option<&Clause> {
125        self.clauses.iter().find(|clause| clause.id == id)
126    }
127
128    /// Returns the candidate clauses that could resolve `goal`.
129    ///
130    /// When `indexing` is true the first-argument index is consulted before
131    /// falling back to the predicate index.
132    pub fn clauses_for_goal(&self, goal: &Expr, indexing: bool) -> Result<Vec<&Clause>> {
133        let predicate = predicate_symbol(goal)?;
134        let arity = goal_arity(goal)?;
135        let ids = if indexing {
136            let first_key = goal_first_arg(goal)
137                .filter(|expr| !matches!(expr, Expr::Local(_)))
138                .map(canonical_goal_key);
139            if let Some(first_key) = first_key {
140                self.by_first_arg
141                    .get(&(predicate.clone(), arity, first_key))
142                    .cloned()
143                    .or_else(|| self.by_predicate.get(&predicate).cloned())
144                    .unwrap_or_default()
145            } else {
146                self.by_predicate
147                    .get(&predicate)
148                    .cloned()
149                    .unwrap_or_default()
150            }
151        } else {
152            self.by_predicate
153                .get(&predicate)
154                .cloned()
155                .unwrap_or_default()
156        };
157        Ok(ids
158            .into_iter()
159            .filter_map(|id| self.clause_by_id(id))
160            .collect())
161    }
162
163    fn rebuild_indexes(&mut self) -> Result<()> {
164        self.by_predicate.clear();
165        self.by_first_arg.clear();
166        let clauses = std::mem::take(&mut self.clauses);
167        for clause in clauses {
168            self.assert_clause(clause)?;
169        }
170        Ok(())
171    }
172}
173
174fn canonical_goal_key(expr: &Expr) -> String {
175    format!("{:?}", normalize_goal_expr(expr).canonical_key())
176}