Skip to main content

logru/resolve/
arithmetic.rs

1use std::collections::HashMap;
2use std::convert::TryInto;
3
4use crate::ast::Sym;
5use crate::search::{Resolved, Resolver, SolutionState};
6use crate::term_arena::{AppTerm, ArgRange, Term, TermId};
7use crate::universe::SymbolStorage;
8
9/// A special resolver for integer arithmetic. It provides a special predicate `is/2` which
10/// evaluates integer expressions.
11///
12/// The second argument of `is/2` must be a integer expression consisting of the terms below. When
13/// the first argument is:
14/// - an unbound variable, it will be bound to the result
15/// - an integer, the predicate succeeds if and only if the result is equal to that integer.
16///
17/// Expressions are represented using an integer term, or one of the following compound terms, which
18/// each take two expressions as arguments:
19/// - `add/2`: addition
20/// - `sub/2`: subtraction
21/// - `mul/2`: multiplication
22/// - `div/2`: division
23/// - `rem/2`: remainder
24///
25/// Notably, free variables are not allowed in those expressions.
26///
27/// Integer overflow errors will fail the `is/2` predicate.
28///
29/// # Examples
30///
31/// - Computing the result of an expression and binding it to `X`:
32///   ```prolog
33///   is(X, add(2, 3)).
34///   ```
35/// - Comparing `4` to the result of the expression (predicate succeeds):
36///   ```prolog
37///   is(4, mul(2, 2)).
38///   ```
39/// - Comparing `4` to the result of the expression (predicate fails):
40///   ```prolog
41///   is(4, add(1, 2)).
42///   ```
43#[derive(Clone)]
44pub struct ArithmeticResolver {
45    exp_map: HashMap<Sym, Exp>,
46    pred_map: HashMap<Sym, Pred>,
47}
48
49impl ArithmeticResolver {
50    pub fn new<T: SymbolStorage>(symbols: &mut T) -> Self {
51        let exps = [
52            ("add", Exp::Add),
53            ("sub", Exp::Sub),
54            ("mul", Exp::Mul),
55            ("div", Exp::Div),
56            ("rem", Exp::Rem),
57            ("pow", Exp::Pow),
58        ];
59        let preds = [("is", Pred::Is)];
60        Self {
61            exp_map: symbols.build_sym_map(exps),
62            pred_map: symbols.build_sym_map(preds),
63        }
64    }
65
66    fn eval_exp(&self, solution: &SolutionState, exp: TermId) -> Option<i64> {
67        // TODO: evaluate expressions iteratively to prevent stack overflows
68        match solution.follow_vars(exp).1 {
69            // TODO: log: an unbound variable is an error
70            Term::Var(_) => None,
71            Term::App(AppTerm(sym, arg_range)) => {
72                let op = self.exp_map.get(&sym)?;
73                let [a1, a2] = solution.terms().get_args_fixed(arg_range)?;
74                let v1 = self.eval_exp(solution, a1)?;
75                let v2 = self.eval_exp(solution, a2)?;
76                // TODO: log overflow errors
77                let ret = match op {
78                    Exp::Add => v1.checked_add(v2)?,
79                    Exp::Sub => v1.checked_sub(v2)?,
80                    Exp::Mul => v1.checked_mul(v2)?,
81                    Exp::Div => v1.checked_div(v2)?,
82                    Exp::Rem => v1.checked_rem(v2)?,
83                    Exp::Pow => v1.checked_pow(v2.try_into().ok()?)?,
84                };
85                Some(ret)
86            }
87            Term::Int(i) => Some(i),
88            // TODO: log: any other term is an error
89            _ => None,
90        }
91    }
92
93    fn resolve_is(
94        &mut self,
95        args: ArgRange,
96        context: &mut crate::search::ResolveContext,
97    ) -> Option<Resolved<()>> {
98        let [left, right] = context.solution().terms().get_args_fixed(args)?;
99        // Right must be fully instantiated and evaluate to integer formula
100        let right_val = self.eval_exp(context.solution(), right)?;
101
102        // Left must be variable or integer
103        let (_left_id, left_term) = context.solution().follow_vars(left);
104        match left_term {
105            Term::Var(var) => {
106                // Allocate result and assign to unbound variable
107                let result_term = context.solution_mut().terms_mut().int(right_val);
108                context
109                    .solution_mut()
110                    .set_var(var, result_term)
111                    .then_some(Resolved::Success)
112            }
113            Term::Int(left_val) => (left_val == right_val).then_some(Resolved::Success),
114            // TODO: log invalid terms
115            _ => None,
116        }
117    }
118}
119
120#[derive(Clone)]
121enum Exp {
122    Add,
123    Sub,
124    Mul,
125    Div,
126    Rem,
127    Pow,
128}
129
130#[derive(Clone)]
131enum Pred {
132    Is,
133}
134
135impl Resolver for ArithmeticResolver {
136    /// The arithmetic resolver provides no choice.
137    type Choice = ();
138
139    fn resolve(
140        &mut self,
141        _goal_id: crate::term_arena::TermId,
142        AppTerm(sym, args): crate::term_arena::AppTerm,
143        context: &mut crate::search::ResolveContext,
144    ) -> Option<Resolved<Self::Choice>> {
145        let pred = self.pred_map.get(&sym)?;
146        match pred {
147            Pred::Is => self.resolve_is(args, context),
148        }
149    }
150
151    fn resume(
152        &mut self,
153        _choice: &mut Self::Choice,
154        _goal_id: crate::term_arena::TermId,
155        _context: &mut crate::search::ResolveContext,
156    ) -> bool {
157        false
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use crate::ast::Term;
164    use crate::query_dfs;
165    use crate::resolve::ResolverExt;
166    use crate::search::Solution;
167    use crate::textual::TextualUniverse;
168
169    use super::ArithmeticResolver;
170
171    #[test]
172    fn simple() {
173        let tu = TextualUniverse::new();
174        let mut query = tu
175            .prepare_query("is(X, add(3, mul(3, sub(6, div(10, rem(10, pow(2,3))))))).")
176            .unwrap();
177        let resolver = ArithmeticResolver::new(&mut query.symbols_mut());
178        let mut results = query_dfs(resolver.or_else(tu.resolver()), query.query());
179        assert_eq!(results.next(), Some(Solution(vec![Some(Term::Int(6))])));
180        assert!(results.next().is_none());
181    }
182
183    #[test]
184    fn complex() {
185        let mut tu = TextualUniverse::new();
186        let mut arith = ArithmeticResolver::new(&mut tu.symbols);
187        tu.load_str(
188            r"
189        eq(Exp1, Exp2) :- is(X, Exp1), is(X, Exp2), !.
190        eq(Exp1, Exp2) :- is(Exp1, Exp2), !.
191        eq(Exp1, Exp2) :- is(Exp2, Exp1), !.
192        ",
193        )
194        .unwrap();
195        {
196            let query = tu.prepare_query("eq(add(2, 2), pow(2, 2)).").unwrap();
197            let mut results = query_dfs(arith.by_ref().or_else(tu.resolver()), query.query());
198            assert_eq!(results.next(), Some(Solution(vec![])));
199            assert!(results.next().is_none());
200        }
201        {
202            let query = tu.prepare_query("eq(X, pow(2, 2)).").unwrap();
203            let mut results = query_dfs(arith.by_ref().or_else(tu.resolver()), query.query());
204            assert_eq!(results.next(), Some(Solution(vec![Some(Term::Int(4))])));
205            assert!(results.next().is_none());
206        }
207        {
208            let query = tu.prepare_query("eq(add(2, 2), X).").unwrap();
209            let mut results = query_dfs(arith.by_ref().or_else(tu.resolver()), query.query());
210            assert_eq!(results.next(), Some(Solution(vec![Some(Term::Int(4))])));
211            assert!(results.next().is_none());
212        }
213        {
214            let query = tu.prepare_query("eq(2, 2).").unwrap();
215            let mut results = query_dfs(arith.by_ref().or_else(tu.resolver()), query.query());
216            assert_eq!(results.next(), Some(Solution(vec![])));
217            assert!(results.next().is_none());
218        }
219    }
220}