Skip to main content

z3/
tactic.rs

1use std::convert::TryFrom;
2use std::ffi::{CStr, CString};
3use std::fmt;
4use std::os::raw::c_uint;
5use std::result::Result;
6use std::str::Utf8Error;
7use std::time::Duration;
8use z3_sys::*;
9
10use crate::{ApplyResult, Context, Goal, Params, Probe, Solver, Tactic};
11
12impl ApplyResult {
13    unsafe fn wrap(ctx: &Context, z3_apply_result: Z3_apply_result) -> ApplyResult {
14        unsafe {
15            Z3_apply_result_inc_ref(ctx.z3_ctx.0, z3_apply_result);
16        }
17        ApplyResult {
18            ctx: ctx.clone(),
19            z3_apply_result,
20        }
21    }
22
23    pub fn list_subgoals(self) -> impl Iterator<Item = Goal> {
24        let num_subgoals =
25            unsafe { Z3_apply_result_get_num_subgoals(self.ctx.z3_ctx.0, self.z3_apply_result) };
26        (0..num_subgoals).map(move |i| unsafe {
27            Goal::wrap(
28                &self.ctx,
29                Z3_apply_result_get_subgoal(self.ctx.z3_ctx.0, self.z3_apply_result, i).unwrap(),
30            )
31        })
32    }
33}
34
35impl Drop for ApplyResult {
36    fn drop(&mut self) {
37        unsafe {
38            Z3_apply_result_dec_ref(self.ctx.z3_ctx.0, self.z3_apply_result);
39        }
40    }
41}
42
43impl Tactic {
44    /// Iterate through the valid tactic names.
45    ///
46    /// # Example
47    ///
48    /// ```
49    /// # use z3::Tactic;
50    /// let tactics: Vec<_> = Tactic::list_all().into_iter().filter_map(|r| r.ok()).collect();
51    /// assert!(tactics.contains(&"ufbv".to_string()));
52    /// ```
53    pub fn list_all() -> Vec<Result<String, Utf8Error>> {
54        let ctx = &Context::thread_local();
55        let p = unsafe { Z3_get_num_tactics(ctx.z3_ctx.0) };
56        (0..p)
57            .map(move |n| {
58                let t = unsafe { Z3_get_tactic_name(ctx.z3_ctx.0, n) };
59                unsafe { CStr::from_ptr(t) }.to_str().map(String::from)
60            })
61            .collect()
62    }
63
64    unsafe fn wrap(ctx: &Context, z3_tactic: Z3_tactic) -> Tactic {
65        unsafe {
66            Z3_tactic_inc_ref(ctx.z3_ctx.0, z3_tactic);
67        }
68        Tactic {
69            ctx: ctx.clone(),
70            z3_tactic,
71        }
72    }
73
74    /// Create a tactic by name.
75    ///
76    /// # Example
77    ///
78    /// ```
79    /// # use z3::Tactic;
80    /// let tactic = Tactic::new("nlsat");
81    /// ```
82    ///
83    /// # See also
84    ///
85    /// - [`Tactic::list_all()`]
86    pub fn new(name: &str) -> Tactic {
87        let ctx = &Context::thread_local();
88        let tactic_name = CString::new(name).unwrap();
89
90        unsafe {
91            let tactic = Z3_mk_tactic(ctx.z3_ctx.0, tactic_name.as_ptr())
92                .unwrap_or_else(|| panic!("{name} is an invalid tactic"));
93            Self::wrap(ctx, tactic)
94        }
95    }
96
97    /// Return a tactic that just return the given goal.
98    pub fn create_skip() -> Tactic {
99        let ctx = &Context::thread_local();
100        unsafe { Self::wrap(ctx, Z3_tactic_skip(ctx.z3_ctx.0).unwrap()) }
101    }
102
103    /// Return a tactic that always fails.
104    pub fn create_fail() -> Tactic {
105        let ctx = &Context::thread_local();
106        unsafe { Self::wrap(ctx, Z3_tactic_fail(ctx.z3_ctx.0).unwrap()) }
107    }
108
109    /// Return a tactic that keeps applying `t` until the goal is not modified anymore or the maximum
110    /// number of iterations `max` is reached.
111    pub fn repeat(t: &Tactic, max: u32) -> Tactic {
112        let ctx = &Context::thread_local();
113        unsafe {
114            Self::wrap(
115                ctx,
116                Z3_tactic_repeat(ctx.z3_ctx.0, t.z3_tactic, max).unwrap(),
117            )
118        }
119    }
120
121    /// Return a tactic that applies the current tactic to a given goal, failing
122    /// if it doesn't terminate within the period specified by `timeout`.
123    pub fn try_for(&self, timeout: Duration) -> Tactic {
124        let timeout_ms = c_uint::try_from(timeout.as_millis()).unwrap_or(c_uint::MAX);
125        unsafe {
126            Self::wrap(
127                &self.ctx,
128                Z3_tactic_try_for(self.ctx.z3_ctx.0, self.z3_tactic, timeout_ms).unwrap(),
129            )
130        }
131    }
132
133    /// Return a tactic that applies the current tactic to a given goal and
134    /// the `then_tactic` to every subgoal produced by the original tactic.
135    pub fn and_then(&self, then_tactic: &Tactic) -> Tactic {
136        unsafe {
137            Self::wrap(
138                &self.ctx,
139                Z3_tactic_and_then(self.ctx.z3_ctx.0, self.z3_tactic, then_tactic.z3_tactic)
140                    .unwrap(),
141            )
142        }
143    }
144
145    /// Return a tactic that current tactic to a given goal,
146    /// if it fails then returns the result of `else_tactic` applied to the given goal.
147    pub fn or_else(&self, else_tactic: &Tactic) -> Tactic {
148        unsafe {
149            Self::wrap(
150                &self.ctx,
151                Z3_tactic_or_else(self.ctx.z3_ctx.0, self.z3_tactic, else_tactic.z3_tactic)
152                    .unwrap(),
153            )
154        }
155    }
156
157    /// Return a tactic that applies self to a given goal if the probe `p` evaluates to true,
158    /// and `t` if `p` evaluates to false.
159    pub fn probe_or_else(&self, p: &Probe, t: &Tactic) -> Tactic {
160        unsafe {
161            Self::wrap(
162                &self.ctx,
163                Z3_tactic_cond(self.ctx.z3_ctx.0, p.z3_probe, self.z3_tactic, t.z3_tactic).unwrap(),
164            )
165        }
166    }
167
168    /// Return a tactic that applies itself to a given goal if the probe `p` evaluates to true.
169    /// If `p` evaluates to false, then the new tactic behaves like the skip tactic.
170    pub fn when(&self, p: &Probe) -> Tactic {
171        unsafe {
172            Self::wrap(
173                &self.ctx,
174                Z3_tactic_when(self.ctx.z3_ctx.0, p.z3_probe, self.z3_tactic).unwrap(),
175            )
176        }
177    }
178
179    /// Return a tactic that applies `t1` to a given goal if the probe `p` evaluates to true,
180    /// and `t2` if `p` evaluates to false.
181    pub fn cond(p: &Probe, t1: &Tactic, t2: &Tactic) -> Tactic {
182        assert_eq!(p.ctx, t1.ctx);
183        assert_eq!(t1.ctx, t2.ctx);
184        unsafe {
185            Self::wrap(
186                &p.ctx,
187                Z3_tactic_cond(p.ctx.z3_ctx.0, p.z3_probe, t1.z3_tactic, t2.z3_tactic).unwrap(),
188            )
189        }
190    }
191
192    /// Return a tactic that fails if the probe `p` evaluates to false.
193    pub fn fail_if(p: &Probe) -> Tactic {
194        unsafe {
195            Self::wrap(
196                &p.ctx,
197                Z3_tactic_fail_if(p.ctx.z3_ctx.0, p.z3_probe).unwrap(),
198            )
199        }
200    }
201
202    /// Attempts to apply the tactic to `goal`. If the tactic succeeds, returns
203    /// `Ok(_)` with a `ApplyResult`. If the tactic fails, returns `Err(_)` with
204    /// an error message describing why.
205    pub fn apply(&self, goal: &Goal, params: Option<&Params>) -> Result<ApplyResult, String> {
206        unsafe {
207            let z3_apply_result = match params {
208                None => Z3_tactic_apply(self.ctx.z3_ctx.0, self.z3_tactic, goal.z3_goal),
209                Some(params) => Z3_tactic_apply_ex(
210                    self.ctx.z3_ctx.0,
211                    self.z3_tactic,
212                    goal.z3_goal,
213                    params.z3_params,
214                ),
215            };
216            if let Some(z3_apply_result) = z3_apply_result {
217                Ok(ApplyResult::wrap(&self.ctx, z3_apply_result))
218            } else {
219                let code = Z3_get_error_code(self.ctx.z3_ctx.0);
220                let msg = Z3_get_error_msg(self.ctx.z3_ctx.0, code);
221                Err(String::from(CStr::from_ptr(msg).to_str().unwrap_or(
222                    "Couldn't retrieve error message from z3: got invalid UTF-8",
223                )))
224            }
225        }
226    }
227
228    /// Create a new solver that is implemented using the given tactic.
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// # use z3::{ast, SatResult, Tactic};
234    ///
235    /// let tactic = Tactic::new("qfnra");
236    /// let solver = tactic.solver();
237    ///
238    /// let x = ast::Int::new_const("x");
239    /// let y = ast::Int::new_const("y");
240    ///
241    /// solver.assert(&x.gt(&y));
242    /// assert_eq!(solver.check(), SatResult::Sat);
243    /// ```
244    ///
245    pub fn solver(&self) -> Solver {
246        unsafe {
247            Solver::wrap(
248                &self.ctx,
249                Z3_mk_solver_from_tactic(self.ctx.z3_ctx.0, self.z3_tactic).unwrap(),
250            )
251        }
252    }
253}
254
255impl fmt::Display for Tactic {
256    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
257        let p = unsafe { Z3_tactic_get_help(self.ctx.z3_ctx.0, self.z3_tactic) };
258        if p.is_null() {
259            return Result::Err(fmt::Error);
260        }
261        match unsafe { CStr::from_ptr(p) }.to_str() {
262            Ok(s) => write!(f, "{s}"),
263            Err(_) => Result::Err(fmt::Error),
264        }
265    }
266}
267
268impl fmt::Debug for Tactic {
269    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
270        <Self as fmt::Display>::fmt(self, f)
271    }
272}
273
274impl Drop for Tactic {
275    fn drop(&mut self) {
276        unsafe {
277            Z3_tactic_dec_ref(self.ctx.z3_ctx.0, self.z3_tactic);
278        }
279    }
280}