[−][src]Module rsmt2_zz::example::simple
A simple example
This example uses the types defined in this module, they will systematically be imported in the code samples.
We first need to define the expression type, and make it implement the Expr2Smt
trait that writes it as an SMT-LIB 2 expression in a writer.
Print functions
Since the structure for S-expressions is provided by users, they also need to provide functions to print it in SMT Lib 2.
To use all SMT Lib 2 commands in a type-safe manner, the library requires printers over
- sorts:
Sort2Smt
trait (e.g. fordeclare-fun
), - symbols:
Sym2Smt
trait (e.g. fordeclare-fun
), - expressions:
Expr2Smt
trait (e.g. forassert
).
All user-provided printing functions take some information. That way, users can pass some
information to, say, assert
that can modify printing. This is typically used when dealing with
transition systems to perform "print-time unrolling". See the
example::print_time
module if you're interested; the example below will not
use print-time information.
extern crate rsmt2 ; use rsmt2::print::Expr2Smt ; use rsmt2::SmtRes ; use rsmt2::example::simple::{ Op, Cst } ; /// An example of expression. pub enum Expr { /// A constant. C(Cst), /// Variable. V(String), /// Operator application. O( Op, Vec<Expr> ), } impl Expr { pub fn cst<C: Into<Cst>>(c: C) -> Self { Expr::C( c.into() ) } } impl Expr2Smt<()> for Expr { fn expr_to_smt2<Writer>( & self, w: & mut Writer, _: () ) -> SmtRes<()> where Writer: ::std::io::Write { let mut stack = vec![ (false, vec![self], false) ] ; while let Some((space, mut to_write, closing_paren)) = stack.pop() { if let Some(next) = to_write.pop() { if space { write!(w, " ") ? } // We have something to print, push the rest back. stack.push((space, to_write, closing_paren)) ; match * next { Expr::C(cst) => write!(w, "{}", cst) ?, Expr::V(ref var) => write!(w, "{}", var) ?, Expr::O(op, ref sub_terms) => { write!(w, "({}", op) ? ; stack.push((true, sub_terms.iter().rev().collect(), true)) }, } } else { // No more things to write at this level. if closing_paren { write!(w, ")") ? } } } Ok(()) } }
For convenience, all the ...2Smt
traits are implemented for & str
. This is useful for
testing and maybe very simple application. Here, we won't implement Sym2Smt
or Sort2Smt
and rely on & str
for symbols and sorts. Using a solver then boils down to creating a
Solver
which wraps a z3 process and provides most of the SMT-LIB 2.5 commands.
extern crate rsmt2 ; use rsmt2::Solver ; use rsmt2::example::simple::{ Op, Cst, Expr } ; let mut solver = Solver::default(()).expect( "could not spawn solver kid" ) ; let v_1 = "v_1".to_string() ; let v_2 = "v_2".to_string() ; solver.declare_const( & v_1, & "Bool" ).expect( "while declaring v_1" ) ; solver.declare_const( & v_2, & "Int" ).expect( "while declaring v_2" ) ; let expr = Expr::O( Op::Disj, vec![ Expr::O( Op::Ge, vec![ Expr::cst(-7), Expr::V( v_2.clone() ) ] ), Expr::V( v_1.clone() ) ] ) ; solver.assert( & expr ).expect( "while asserting an expression" ) ; if solver.check_sat().expect("during check sat") { () } else { panic!("expected sat, got unsat") } solver.kill().unwrap()
Note the unit
parameter that we passed to the solver
function: solver(& mut kid, ())
. This
is actually the parser the solver should use when it needs to parse values, symbols, types... In
the example above, we only asked for the satisfiability of the assertions. If we had asked for a
model, the compiler would have complained by saying that our parser ()
does not implement the
right parsing traits.
The parser
This example will only use get_model
, which only requires IdentParser
and ModelParser
. In
most cases, an empty parser struct
with the right implementations should be enough.
extern crate rsmt2 ; use rsmt2::SmtRes ; use rsmt2::parse::{ IdentParser, ModelParser } ; use rsmt2::example::simple::Cst ; /// Empty parser structure, we will not maintain any context. #[derive(Clone, Copy)] pub struct Parser ; impl<'a> IdentParser<String, String, & 'a str> for Parser { fn parse_ident(self, input: & 'a str) -> SmtRes<String> { Ok( input.to_string() ) } fn parse_type(self, input: & 'a str) -> SmtRes<String> { match input { "Int" => Ok( "Int".into() ), "Bool" => Ok( "Bool".into() ), sort => bail!("unexpected sort `{}`", sort), } } } impl<'a> ModelParser<String, String, Cst, & 'a str> for Parser { fn parse_value( self, input: & 'a str, _ident: & String, _signature: & [ (String, String) ], _type: & String, ) -> SmtRes<Cst> { match input.trim() { "true" => Ok( Cst::B(true) ), "false" => Ok( Cst::B(false) ), int => { use std::str::FromStr ; let s = int.trim() ; if let Ok(res) = isize::from_str(s) { return Ok( Cst::I(res) ) } else if s.len() >= 4 { if & s[0 .. 1] == "(" && & s[s.len() - 1 ..] == ")" { let s = & s[1 .. s.len() - 1].trim() ; if & s[0 .. 1] == "-" { let s = & s[1..].trim() ; if let Ok(res) = isize::from_str(s) { return Ok( Cst::I(- res) ) } } } } bail!("unexpected value `{}`", int) }, } } }
As a side note, it would have been simpler to implement ModelParser
with a & mut SmtParser
, as it provides the parsers we needed.
use rsmt2::SmtRes ; use rsmt2::parse::{ SmtParser, IdentParser, ModelParser } ; use rsmt2::example::simple::Cst ; #[derive(Clone, Copy)] struct Parser ; impl<'a, Br> ModelParser< String, String, Cst, & 'a mut SmtParser<Br> > for Parser where Br: ::std::io::BufRead { fn parse_value( self, input: & 'a mut SmtParser<Br>, _ident: & String, _signature: & [ (String, String) ], _type: & String ) -> SmtRes<Cst> { use std::str::FromStr ; if let Some(b) = input.try_bool() ? { Ok( Cst::B(b) ) } else if let Some(int) = input.try_int( |int, pos| match isize::from_str(int) { Ok(int) => if pos { Ok(int) } else { Ok(- int) }, Err(e) => Err(e), } ) ? { Ok( Cst::I(int) ) } else { input.fail_with("unexpected value") } } }
Anyway, once we pass Parser
to the solver creation function, and all conditions are met to ask
the solver for a model.
extern crate rsmt2 ; use rsmt2::{ SmtRes, Solver } ; use rsmt2::example::simple::{ Cst, Op, Expr, Parser } ; let mut solver = Solver::default(Parser).expect( "could not spawn solver kid" ) ; let v_1 = "v_1".to_string() ; let v_2 = "v_2".to_string() ; solver.declare_const( & v_1, & "Bool" ).expect( "while declaring v_1" ) ; solver.declare_const( & v_2, & "Int" ).expect( "while declaring v_2" ) ; let expr = Expr::O( Op::Disj, vec![ Expr::O( Op::Ge, vec![ Expr::cst(-7), Expr::V( v_2.clone() ) ] ), Expr::V( v_1.clone() ) ] ) ; solver.assert( & expr ).expect( "while asserting an expression" ) ; if solver.check_sat().expect("during check sat") { let model = solver.get_model_const().expect( "while getting model" ) ; let mut okay = false ; for (ident, typ, value) in model { if ident == v_1 { assert_eq!( typ, "Bool" ) ; match value { Cst::B(true) => okay = true, Cst::B(false) => (), Cst::I(int) => panic!( "value for v_1 is `{}`, expected boolean", int ), } } else if ident == v_2 { assert_eq!( typ, "Int" ) ; match value { Cst::I(i) if -7 >= i => okay = true, Cst::I(_) => (), Cst::B(b) => panic!( "value for v_2 is `{}`, expected isize", b ), } } } if ! okay { panic!("got sat, but model is spurious") } } else { panic!("expected sat, got unsat") } solver.kill().unwrap()
Structs
Parser | Empty parser structure, we will not maintain any context. |
Enums
Cst | A constant. |
Expr | An example of expression. |
Op | Operators. Just implements |