use std::any::Any; use std::collections::HashSet; mod bool_ops; mod internal_macros; pub mod iter; mod num_ops; mod traits; pub use bool_ops::*; pub use num_ops::*; pub use traits::*; use self::iter::AstIter; use crate::{ArgusResult, Error}; /// All expressions that are numeric #[derive(Clone, Debug, PartialEq)] pub enum NumExpr { IntLit(i64), UIntLit(u64), FloatLit(f64), IntVar { name: String }, UIntVar { name: String }, FloatVar { name: String }, Neg { arg: Box }, Add { args: Vec }, Mul { args: Vec }, Div { dividend: Box, divisor: Box }, } impl Expr for NumExpr { fn is_numeric(&self) -> bool { true } fn is_boolean(&self) -> bool { false } fn args(&self) -> Vec> { match self { NumExpr::Neg { arg } => vec![arg.as_ref().into()], NumExpr::Add { args } | NumExpr::Mul { args } => args.iter().map(|arg| arg.into()).collect(), NumExpr::Div { dividend, divisor } => vec![dividend.as_ref().into(), divisor.as_ref().into()], _ => vec![], } } fn as_any(&self) -> &dyn Any { self } fn iter(&self) -> iter::AstIter<'_> { AstIter::new(self.into()) } } /// Types of comparison operations #[derive(Clone, Copy, Debug, PartialEq)] pub enum Ordering { Eq, NotEq, Less { strict: bool }, Greater { strict: bool }, } /// All expressions that are evaluated to be of type `bool` #[derive(Clone, Debug, PartialEq)] pub enum BoolExpr { BoolLit(bool), BoolVar { name: String }, Cmp { op: Ordering, lhs: Box, rhs: Box }, Not { arg: Box }, And { args: Vec }, Or { args: Vec }, } impl Expr for BoolExpr { fn is_numeric(&self) -> bool { false } fn is_boolean(&self) -> bool { true } fn args(&self) -> Vec> { match self { BoolExpr::Cmp { op: _, lhs, rhs } => vec![lhs.as_ref().into(), rhs.as_ref().into()], BoolExpr::Not { arg } => vec![arg.as_ref().into()], BoolExpr::And { args } | BoolExpr::Or { args } => args.iter().map(|arg| arg.into()).collect(), _ => vec![], } } fn as_any(&self) -> &dyn Any { self } fn iter(&self) -> AstIter<'_> { AstIter::new(self.into()) } } #[derive(Clone, Copy, Debug, derive_more::From)] pub enum ExprRef<'a> { Bool(&'a BoolExpr), Num(&'a NumExpr), } /// Expression builder /// /// The `ExprBuilder` is a factory structure that deals with the creation of /// expressions. The main goal of this is to ensure users do not create duplicate /// definitions for variables. #[derive(Clone, Debug, Default)] pub struct ExprBuilder { declarations: HashSet, } impl ExprBuilder { pub fn new() -> Self { Self { declarations: Default::default(), } } pub fn bool_const(&self, value: bool) -> Box { Box::new(BoolExpr::BoolLit(value)) } pub fn int_const(&self, value: i64) -> Box { Box::new(NumExpr::IntLit(value)) } pub fn uint_const(&self, value: u64) -> Box { Box::new(NumExpr::UIntLit(value)) } pub fn float_const(&self, value: f64) -> Box { Box::new(NumExpr::FloatLit(value)) } pub fn bool_var(&mut self, name: String) -> ArgusResult> { if self.declarations.insert(name.clone()) { Ok(Box::new(BoolExpr::BoolVar { name })) } else { Err(Error::IdentifierRedeclaration) } } pub fn int_var(&mut self, name: String) -> ArgusResult> { if self.declarations.insert(name.clone()) { Ok(Box::new(NumExpr::IntVar { name })) } else { Err(Error::IdentifierRedeclaration) } } pub fn uint_var(&mut self, name: String) -> ArgusResult> { if self.declarations.insert(name.clone()) { Ok(Box::new(NumExpr::UIntVar { name })) } else { Err(Error::IdentifierRedeclaration) } } pub fn float_var(&mut self, name: String) -> ArgusResult> { if self.declarations.insert(name.clone()) { Ok(Box::new(NumExpr::FloatVar { name })) } else { Err(Error::IdentifierRedeclaration) } } pub fn make_neg(&self, arg: Box) -> Box { Box::new(NumExpr::Neg { arg }) } pub fn make_add(&self, args: I) -> ArgusResult> where I: IntoIterator, { let args: Vec<_> = args.into_iter().collect(); if args.len() < 2 { Err(Error::IncompleteArgs) } else { Ok(Box::new(NumExpr::Add { args })) } } pub fn make_mul(&self, args: I) -> ArgusResult> where I: IntoIterator, { let args: Vec<_> = args.into_iter().collect(); if args.len() < 2 { Err(Error::IncompleteArgs) } else { Ok(Box::new(NumExpr::Mul { args })) } } pub fn make_div(&self, dividend: Box, divisor: Box) -> Box { Box::new(NumExpr::Div { dividend, divisor }) } pub fn make_cmp(&self, op: Ordering, lhs: Box, rhs: Box) -> Box { Box::new(BoolExpr::Cmp { op, lhs, rhs }) } pub fn make_lt(&self, lhs: Box, rhs: Box) -> Box { self.make_cmp(Ordering::Less { strict: true }, lhs, rhs) } pub fn make_le(&self, lhs: Box, rhs: Box) -> Box { self.make_cmp(Ordering::Less { strict: false }, lhs, rhs) } pub fn make_gt(&self, lhs: Box, rhs: Box) -> Box { self.make_cmp(Ordering::Greater { strict: true }, lhs, rhs) } pub fn make_ge(&self, lhs: Box, rhs: Box) -> Box { self.make_cmp(Ordering::Greater { strict: false }, lhs, rhs) } pub fn make_eq(&self, lhs: Box, rhs: Box) -> Box { self.make_cmp(Ordering::Eq, lhs, rhs) } pub fn make_neq(&self, lhs: Box, rhs: Box) -> Box { self.make_cmp(Ordering::NotEq, lhs, rhs) } pub fn make_not(&self, arg: Box) -> Box { Box::new(BoolExpr::Not { arg }) } pub fn make_or(&self, args: I) -> ArgusResult> where I: IntoIterator, { let args: Vec<_> = args.into_iter().collect(); if args.len() < 2 { Err(Error::IncompleteArgs) } else { Ok(Box::new(BoolExpr::Or { args })) } } pub fn make_and(&self, args: I) -> ArgusResult> where I: IntoIterator, { let args: Vec<_> = args.into_iter().collect(); if args.len() < 2 { Err(Error::IncompleteArgs) } else { Ok(Box::new(BoolExpr::And { args })) } } } #[cfg(test)] pub mod arbitrary { //! Helper functions to generate arbitrary expressions using [`proptest`]. use proptest::prelude::*; use super::*; pub fn num_expr() -> impl Strategy> { let leaf = prop_oneof![ any::().prop_map(|val| Box::new(NumExpr::IntLit(val))), any::().prop_map(|val| Box::new(NumExpr::UIntLit(val))), any::().prop_map(|val| Box::new(NumExpr::FloatLit(val))), "[[:word:]]*".prop_map(|name| Box::new(NumExpr::IntVar { name })), "[[:word:]]*".prop_map(|name| Box::new(NumExpr::UIntVar { name })), "[[:word:]]*".prop_map(|name| Box::new(NumExpr::FloatVar { name })), ]; leaf.prop_recursive( 8, // 8 levels deep 128, // Shoot for maximum size of 256 nodes 10, // We put up to 10 items per collection |inner| { prop_oneof![ inner.clone().prop_map(|arg| Box::new(NumExpr::Neg { arg })), prop::collection::vec(inner.clone(), 0..10).prop_map(|args| { Box::new(NumExpr::Add { args: args.into_iter().map(|arg| *arg).collect(), }) }), prop::collection::vec(inner.clone(), 0..10).prop_map(|args| { Box::new(NumExpr::Mul { args: args.into_iter().map(|arg| *arg).collect(), }) }), (inner.clone(), inner) .prop_map(|(dividend, divisor)| { Box::new(NumExpr::Div { dividend, divisor }) }) ] }, ) } pub fn cmp_expr() -> impl Strategy> { use Ordering::*; let op = prop_oneof![Just(Eq), Just(NotEq),]; let lhs = num_expr(); let rhs = num_expr(); (op, lhs, rhs).prop_map(|(op, lhs, rhs)| Box::new(BoolExpr::Cmp { op, lhs, rhs })) } pub fn bool_expr() -> impl Strategy> { let leaf = prop_oneof![ any::().prop_map(|val| Box::new(BoolExpr::BoolLit(val))), "[[:word:]]*".prop_map(|name| Box::new(BoolExpr::BoolVar { name })), cmp_expr(), ]; leaf.prop_recursive( 8, // 8 levels deep 128, // Shoot for maximum size of 256 nodes 10, // We put up to 10 items per collection |inner| { prop_oneof![ inner.clone().prop_map(|arg| Box::new(BoolExpr::Not { arg })), prop::collection::vec(inner.clone(), 0..10).prop_map(|args| { Box::new(BoolExpr::And { args: args.into_iter().map(|arg| *arg).collect(), }) }), prop::collection::vec(inner, 0..10).prop_map(|args| { Box::new(BoolExpr::Or { args: args.into_iter().map(|arg| *arg).collect(), }) }), ] }, ) } } #[cfg(test)] mod tests { use paste::paste; use proptest::prelude::*; use super::*; proptest! { #[test] fn correctly_create_num_expr(num_expr in arbitrary::num_expr()) { _ = num_expr; } } proptest! { #[test] fn correctly_create_bool_expr(bool_expr in arbitrary::bool_expr()) { _ = bool_expr; } } proptest! { #[test] fn neg_num_expr(arg in arbitrary::num_expr()) { let expr = -arg; assert!(matches!(expr, NumExpr::Neg { arg: _ })); } } macro_rules! test_num_binop { ($name:ident, $method:ident with /) => { paste! { proptest! { #[test] fn [<$method _num_expr>](lhs in arbitrary::num_expr(), rhs in arbitrary::num_expr()) { let expr = lhs / rhs; assert!(matches!(expr, NumExpr::$name {dividend: _, divisor: _ })); } } } }; ($name:ident, $method:ident with $op:tt) => { paste! { proptest! { #[test] fn [<$method _num_expr>](lhs in arbitrary::num_expr(), rhs in arbitrary::num_expr()) { let expr = lhs $op rhs; assert!(matches!(expr, NumExpr::$name { args: _ })); } } } }; } test_num_binop!(Add, add with +); test_num_binop!(Mul, mul with *); test_num_binop!(Div, div with /); proptest! { #[test] fn not_bool_expr(arg in arbitrary::bool_expr()) { let expr = !arg; assert!(matches!(expr, BoolExpr::Not { arg: _ })); } } macro_rules! test_bool_binop { ($name:ident, $method:ident with $op:tt) => { paste! { proptest! { #[test] fn [<$method _bool_expr>](lhs in arbitrary::bool_expr(), rhs in arbitrary::bool_expr()) { let expr = Box::new(lhs $op rhs); assert!(matches!(*expr, BoolExpr::$name { args: _ })); } } } }; } test_bool_binop!(And, bitand with &); test_bool_binop!(Or, bitor with |); }