feat!(core): Use new AST structure
Derive Expr methods using a derive proc-macro. These macros are present in the `argus-derive` crate, but the traits are defined in `argus-core`
This commit is contained in:
parent
70c5a50d22
commit
1c79847a77
22 changed files with 958 additions and 702 deletions
|
|
@ -1,4 +1,10 @@
|
|||
[workspace]
|
||||
members = ["argus", "argus-core", "argus-semantics"]
|
||||
members = [
|
||||
"argus",
|
||||
"argus-core",
|
||||
"argus-semantics",
|
||||
"argus-automata",
|
||||
"argus-derive",
|
||||
]
|
||||
|
||||
exclude = ["pyargus"]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ paste = "1.0.12"
|
|||
num-traits = "0.2.15"
|
||||
thiserror = "1.0.39"
|
||||
proptest = { version = "1.1.0", optional = true }
|
||||
enum_dispatch = "0.3.11"
|
||||
argus-derive = { version = "0.1.0", path = "../argus-derive" }
|
||||
|
||||
[dev-dependencies]
|
||||
argus-core = { path = ".", features = ["arbitrary"] }
|
||||
|
|
|
|||
8
argus-core/proptest-regressions/expr/traits.txt
Normal file
8
argus-core/proptest-regressions/expr/traits.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 400240db9d2e9afecba257e48d5385b34a5dc746c60b34c1621a1f6e8c41893f # shrinks to num_expr = IntLit(IntLit(0))
|
||||
cc 8f1f212537f462eb0d9f46febda2f5d1c57b60596290a70e9acca0d4162e90f5 # shrinks to bool_expr = Not(Not { arg: BoolVar(BoolVar { name: "Cz_da6bc_347__" }) })
|
||||
|
|
@ -1,297 +1,86 @@
|
|||
//! Expression tree for Argus specifications
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashSet;
|
||||
use std::ops::{Bound, RangeBounds};
|
||||
use std::ops::Bound;
|
||||
use std::time::Duration;
|
||||
|
||||
mod bool_ops;
|
||||
mod internal_macros;
|
||||
mod bool_expr;
|
||||
pub mod iter;
|
||||
mod num_ops;
|
||||
mod num_expr;
|
||||
mod traits;
|
||||
|
||||
pub use bool_ops::*;
|
||||
pub use num_ops::*;
|
||||
pub use bool_expr::*;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
pub use num_expr::*;
|
||||
pub use traits::*;
|
||||
|
||||
use self::iter::AstIter;
|
||||
use crate::{ArgusResult, Error};
|
||||
|
||||
/// All expressions that are numeric
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
#[enum_dispatch(Expr)]
|
||||
pub enum NumExpr {
|
||||
/// A signed integer literal
|
||||
IntLit(i64),
|
||||
IntLit(IntLit),
|
||||
/// An unsigned integer literal
|
||||
UIntLit(u64),
|
||||
UIntLit(UIntLit),
|
||||
/// A floating point literal
|
||||
FloatLit(f64),
|
||||
FloatLit(FloatLit),
|
||||
/// A signed integer variable
|
||||
IntVar {
|
||||
/// Name of the variable
|
||||
name: String,
|
||||
},
|
||||
IntVar(IntVar),
|
||||
/// A unsigned integer variable
|
||||
UIntVar {
|
||||
/// Name of the variable
|
||||
name: String,
|
||||
},
|
||||
UIntVar(UIntVar),
|
||||
/// A floating point number variable
|
||||
FloatVar {
|
||||
/// Name of the variable
|
||||
name: String,
|
||||
},
|
||||
FloatVar(FloatVar),
|
||||
/// Numeric negation of a numeric expression
|
||||
Neg {
|
||||
/// Numeric expression being negated
|
||||
arg: Box<NumExpr>,
|
||||
},
|
||||
Neg(Neg),
|
||||
/// Arithmetic addition of a list of numeric expressions
|
||||
Add {
|
||||
/// List of expressions being added
|
||||
args: Vec<NumExpr>,
|
||||
},
|
||||
Add(Add),
|
||||
/// Subtraction of two numbers
|
||||
Sub {
|
||||
/// LHS to the expression `lhs - rhs`
|
||||
lhs: Box<NumExpr>,
|
||||
/// RHS to the expression `lhs - rhs`
|
||||
rhs: Box<NumExpr>,
|
||||
},
|
||||
Sub(Sub),
|
||||
/// Arithmetic multiplication of a list of numeric expressions
|
||||
Mul {
|
||||
/// List of expressions being multiplied
|
||||
args: Vec<NumExpr>,
|
||||
},
|
||||
Mul(Mul),
|
||||
/// Divide two expressions `dividend / divisor`
|
||||
Div {
|
||||
/// The dividend
|
||||
dividend: Box<NumExpr>,
|
||||
/// The divisor
|
||||
divisor: Box<NumExpr>,
|
||||
},
|
||||
Div(Div),
|
||||
/// The absolute value of an expression
|
||||
Abs {
|
||||
/// Argument to `abs`
|
||||
arg: Box<NumExpr>,
|
||||
},
|
||||
Abs(Abs),
|
||||
}
|
||||
|
||||
impl Expr for NumExpr {
|
||||
fn is_numeric(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_boolean(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<ExprRef<'_>> {
|
||||
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<'_> {
|
||||
impl NumExpr {
|
||||
/// Create a borrowed iterator over the expression tree
|
||||
pub fn iter(&self) -> AstIter<'_> {
|
||||
AstIter::new(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Types of comparison operations
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Ordering {
|
||||
/// Equality check for two expressions
|
||||
Eq,
|
||||
/// Non-equality check for two expressions
|
||||
NotEq,
|
||||
/// Less than check
|
||||
Less {
|
||||
/// Denotes `lhs < rhs` if `strict`, and `lhs <= rhs` otherwise.
|
||||
strict: bool,
|
||||
},
|
||||
/// Greater than check
|
||||
Greater {
|
||||
/// Denotes `lhs > rhs` if `strict`, and `lhs >= rhs` otherwise.
|
||||
strict: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Ordering {
|
||||
/// Check if `Ordering::Eq`
|
||||
pub fn equal() -> Self {
|
||||
Self::Eq
|
||||
}
|
||||
|
||||
/// Check if `Ordering::NotEq`
|
||||
pub fn not_equal() -> Self {
|
||||
Self::NotEq
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Less { strict: true }`
|
||||
pub fn less_than() -> Self {
|
||||
Self::Less { strict: true }
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Less { strict: false }`
|
||||
pub fn less_than_eq() -> Self {
|
||||
Self::Less { strict: false }
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Greater { strict: true }`
|
||||
pub fn greater_than() -> Self {
|
||||
Self::Greater { strict: true }
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Less { strict: false }`
|
||||
pub fn greater_than_eq() -> Self {
|
||||
Self::Greater { strict: false }
|
||||
}
|
||||
}
|
||||
|
||||
/// A time interval for a temporal expression.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, derive_more::Into)]
|
||||
#[into(owned, ref, ref_mut)]
|
||||
pub struct Interval {
|
||||
/// Start of the interval
|
||||
pub start: Bound<Duration>,
|
||||
/// End of the interval
|
||||
pub end: Bound<Duration>,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// Create a new interval
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Argus doesn't permit `Interval`s with [`Bound::Excluded(_)`] values (as these
|
||||
/// can't be monitored reliably) and thus converts all such bounds to an
|
||||
/// [`Bound::Included(_)`]. Moreover, if the `start` bound is [`Bound::Unbounded`],
|
||||
/// it will get transformed to [`Bound::Included(Duration::ZERO)`].
|
||||
pub fn new(start: Bound<Duration>, end: Bound<Duration>) -> Self {
|
||||
use Bound::*;
|
||||
let start = match start {
|
||||
a @ Included(_) => a,
|
||||
Excluded(b) => Included(b),
|
||||
Unbounded => Included(Duration::ZERO),
|
||||
};
|
||||
|
||||
let end = match end {
|
||||
Excluded(b) => Included(b),
|
||||
bound => bound,
|
||||
};
|
||||
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
/// Check if the interval is empty
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
use Bound::*;
|
||||
match (&self.start, &self.end) {
|
||||
(Included(a), Included(b)) => a > b,
|
||||
(Included(a), Excluded(b)) | (Excluded(a), Included(b)) | (Excluded(a), Excluded(b)) => a >= b,
|
||||
(Unbounded, Excluded(b)) => b == &Duration::ZERO,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the interval is a singleton
|
||||
///
|
||||
/// This implies that only 1 timepoint is valid within this interval.
|
||||
#[inline]
|
||||
pub fn is_singleton(&self) -> bool {
|
||||
use Bound::*;
|
||||
match (&self.start, &self.end) {
|
||||
(Included(a), Included(b)) => a == b,
|
||||
(Unbounded, Included(b)) => b == &Duration::ZERO,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the interval covers `[0, ..)`.
|
||||
#[inline]
|
||||
pub fn is_untimed(&self) -> bool {
|
||||
use Bound::*;
|
||||
match (self.start, self.end) {
|
||||
(Unbounded, Unbounded) | (Included(Duration::ZERO), Unbounded) => true,
|
||||
(Included(_), Included(_)) | (Included(_), Unbounded) => false,
|
||||
(Excluded(_), _) | (_, Excluded(_)) | (Unbounded, _) => {
|
||||
unreachable!("looks like someone didn't use Interval::new")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Interval
|
||||
where
|
||||
T: RangeBounds<Duration>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value.start_bound().cloned(), value.end_bound().cloned())
|
||||
}
|
||||
}
|
||||
|
||||
/// All expressions that are evaluated to be of type `bool`
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
#[enum_dispatch(Expr)]
|
||||
pub enum BoolExpr {
|
||||
/// A `bool` literal
|
||||
BoolLit(bool),
|
||||
BoolLit(BoolLit),
|
||||
/// A `bool` variable
|
||||
BoolVar {
|
||||
/// Variable name
|
||||
name: String,
|
||||
},
|
||||
BoolVar(BoolVar),
|
||||
/// A comparison expression
|
||||
Cmp {
|
||||
/// The type of comparison
|
||||
op: Ordering,
|
||||
/// The LHS for the comparison
|
||||
lhs: Box<NumExpr>,
|
||||
/// The RHS for the comparison
|
||||
rhs: Box<NumExpr>,
|
||||
},
|
||||
Cmp(Cmp),
|
||||
/// Logical negation of an expression
|
||||
Not {
|
||||
/// Expression to be negated
|
||||
arg: Box<BoolExpr>,
|
||||
},
|
||||
Not(Not),
|
||||
/// Logical conjunction of a list of expressions
|
||||
And {
|
||||
/// Expressions to be "and"-ed
|
||||
args: Vec<BoolExpr>,
|
||||
},
|
||||
And(And),
|
||||
/// Logical disjunction of a list of expressions
|
||||
Or {
|
||||
/// Expressions to be "or"-ed
|
||||
args: Vec<BoolExpr>,
|
||||
},
|
||||
Or(Or),
|
||||
|
||||
/// A temporal next expression
|
||||
///
|
||||
/// Checks if the next time point in a signal is `true` or not.
|
||||
Next {
|
||||
/// Argument for `Next`
|
||||
arg: Box<BoolExpr>,
|
||||
},
|
||||
Next(Next),
|
||||
|
||||
/// Temporal "oracle" expression
|
||||
///
|
||||
/// This is equivalent to `steps` number of nested [`Next`](BoolExpr::Next)
|
||||
/// expressions.
|
||||
Oracle {
|
||||
/// Number of steps to look ahead
|
||||
steps: usize,
|
||||
/// Argument for `Oracle`
|
||||
arg: Box<BoolExpr>,
|
||||
},
|
||||
Oracle(Oracle),
|
||||
|
||||
/// A temporal always expression
|
||||
///
|
||||
|
|
@ -299,12 +88,7 @@ pub enum BoolExpr {
|
|||
/// Unbounded)`: checks if the signal is `true` for all points in a signal.
|
||||
/// - Otherwise: checks if the signal is `true` for all points within the
|
||||
/// `interval`.
|
||||
Always {
|
||||
/// Argument for `Always`
|
||||
arg: Box<BoolExpr>,
|
||||
/// Interval for the expression
|
||||
interval: Interval,
|
||||
},
|
||||
Always(Always),
|
||||
|
||||
/// A temporal eventually expression
|
||||
///
|
||||
|
|
@ -312,49 +96,17 @@ pub enum BoolExpr {
|
|||
/// Unbounded)`: checks if the signal is `true` for some point in a signal.
|
||||
/// - Otherwise: checks if the signal is `true` for some point within the
|
||||
/// `interval`.
|
||||
Eventually {
|
||||
/// Argument for `Eventually`
|
||||
arg: Box<BoolExpr>,
|
||||
/// Interval for the expression
|
||||
interval: Interval,
|
||||
},
|
||||
Eventually(Eventually),
|
||||
|
||||
/// A temporal until expression
|
||||
///
|
||||
/// Checks if the `lhs` is always `true` for a signal until `rhs` becomes `true`.
|
||||
Until {
|
||||
/// LHS to `lhs Until rhs`
|
||||
lhs: Box<BoolExpr>,
|
||||
/// RHS to `lhs Until rhs`
|
||||
rhs: Box<BoolExpr>,
|
||||
/// Interval for the expression
|
||||
interval: Interval,
|
||||
},
|
||||
Until(Until),
|
||||
}
|
||||
|
||||
impl Expr for BoolExpr {
|
||||
fn is_numeric(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_boolean(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<ExprRef<'_>> {
|
||||
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<'_> {
|
||||
impl BoolExpr {
|
||||
/// Create a borrowed iterator over the expression tree
|
||||
pub fn iter(&self) -> AstIter<'_> {
|
||||
AstIter::new(self.into())
|
||||
}
|
||||
}
|
||||
|
|
@ -388,28 +140,28 @@ impl ExprBuilder {
|
|||
|
||||
/// Declare a constant boolean expression
|
||||
pub fn bool_const(&self, value: bool) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::BoolLit(value))
|
||||
Box::new(BoolLit(value).into())
|
||||
}
|
||||
|
||||
/// Declare a constant integer expression
|
||||
pub fn int_const(&self, value: i64) -> Box<NumExpr> {
|
||||
Box::new(NumExpr::IntLit(value))
|
||||
Box::new(IntLit(value).into())
|
||||
}
|
||||
|
||||
/// Declare a constant unsigned integer expression
|
||||
pub fn uint_const(&self, value: u64) -> Box<NumExpr> {
|
||||
Box::new(NumExpr::UIntLit(value))
|
||||
Box::new(UIntLit(value).into())
|
||||
}
|
||||
|
||||
/// Declare a constant floating point expression
|
||||
pub fn float_const(&self, value: f64) -> Box<NumExpr> {
|
||||
Box::new(NumExpr::FloatLit(value))
|
||||
Box::new(FloatLit(value).into())
|
||||
}
|
||||
|
||||
/// Declare a boolean variable
|
||||
pub fn bool_var(&mut self, name: String) -> ArgusResult<Box<BoolExpr>> {
|
||||
if self.declarations.insert(name.clone()) {
|
||||
Ok(Box::new(BoolExpr::BoolVar { name }))
|
||||
Ok(Box::new((BoolVar { name }).into()))
|
||||
} else {
|
||||
Err(Error::IdentifierRedeclaration)
|
||||
}
|
||||
|
|
@ -418,7 +170,7 @@ impl ExprBuilder {
|
|||
/// Declare a integer variable
|
||||
pub fn int_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> {
|
||||
if self.declarations.insert(name.clone()) {
|
||||
Ok(Box::new(NumExpr::IntVar { name }))
|
||||
Ok(Box::new((IntVar { name }).into()))
|
||||
} else {
|
||||
Err(Error::IdentifierRedeclaration)
|
||||
}
|
||||
|
|
@ -427,7 +179,7 @@ impl ExprBuilder {
|
|||
/// Declare a unsigned integer variable
|
||||
pub fn uint_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> {
|
||||
if self.declarations.insert(name.clone()) {
|
||||
Ok(Box::new(NumExpr::UIntVar { name }))
|
||||
Ok(Box::new((UIntVar { name }).into()))
|
||||
} else {
|
||||
Err(Error::IdentifierRedeclaration)
|
||||
}
|
||||
|
|
@ -436,7 +188,7 @@ impl ExprBuilder {
|
|||
/// Declare a floating point variable
|
||||
pub fn float_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> {
|
||||
if self.declarations.insert(name.clone()) {
|
||||
Ok(Box::new(NumExpr::FloatVar { name }))
|
||||
Ok(Box::new((FloatVar { name }).into()))
|
||||
} else {
|
||||
Err(Error::IdentifierRedeclaration)
|
||||
}
|
||||
|
|
@ -444,7 +196,7 @@ impl ExprBuilder {
|
|||
|
||||
/// Create a [`NumExpr::Neg`] expression
|
||||
pub fn make_neg(&self, arg: Box<NumExpr>) -> Box<NumExpr> {
|
||||
Box::new(NumExpr::Neg { arg })
|
||||
Box::new((Neg { arg }).into())
|
||||
}
|
||||
|
||||
/// Create a [`NumExpr::Add`] expression
|
||||
|
|
@ -456,7 +208,7 @@ impl ExprBuilder {
|
|||
if args.len() < 2 {
|
||||
Err(Error::IncompleteArgs)
|
||||
} else {
|
||||
Ok(Box::new(NumExpr::Add { args }))
|
||||
Ok(Box::new((Add { args }).into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,18 +221,18 @@ impl ExprBuilder {
|
|||
if args.len() < 2 {
|
||||
Err(Error::IncompleteArgs)
|
||||
} else {
|
||||
Ok(Box::new(NumExpr::Mul { args }))
|
||||
Ok(Box::new((Mul { args }).into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`NumExpr::Div`] expression
|
||||
pub fn make_div(&self, dividend: Box<NumExpr>, divisor: Box<NumExpr>) -> Box<NumExpr> {
|
||||
Box::new(NumExpr::Div { dividend, divisor })
|
||||
Box::new((Div { dividend, divisor }).into())
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Cmp`] expression
|
||||
pub fn make_cmp(&self, op: Ordering, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Cmp { op, lhs, rhs })
|
||||
Box::new((Cmp { op, lhs, rhs }).into())
|
||||
}
|
||||
|
||||
/// Create a "less than" ([`BoolExpr::Cmp`]) expression
|
||||
|
|
@ -515,7 +267,7 @@ impl ExprBuilder {
|
|||
|
||||
/// Create a [`BoolExpr::Not`] expression.
|
||||
pub fn make_not(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Not { arg })
|
||||
Box::new((Not { arg }).into())
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Or`] expression.
|
||||
|
|
@ -527,7 +279,7 @@ impl ExprBuilder {
|
|||
if args.len() < 2 {
|
||||
Err(Error::IncompleteArgs)
|
||||
} else {
|
||||
Ok(Box::new(BoolExpr::Or { args }))
|
||||
Ok(Box::new((Or { args }).into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -540,58 +292,67 @@ impl ExprBuilder {
|
|||
if args.len() < 2 {
|
||||
Err(Error::IncompleteArgs)
|
||||
} else {
|
||||
Ok(Box::new(BoolExpr::And { args }))
|
||||
Ok(Box::new((And { args }).into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Next`] expression.
|
||||
pub fn make_next(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Next { arg })
|
||||
Box::new((Next { arg }).into())
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Oracle`] expression.
|
||||
pub fn make_oracle(&self, steps: usize, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Oracle { steps, arg })
|
||||
Box::new((Oracle { steps, arg }).into())
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Always`] expression.
|
||||
pub fn make_always(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Always {
|
||||
arg,
|
||||
interval: (..).into(),
|
||||
})
|
||||
Box::new(
|
||||
(Always {
|
||||
arg,
|
||||
interval: (..).into(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Always`] expression with an interval.
|
||||
pub fn make_timed_always(&self, interval: Interval, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Always { arg, interval })
|
||||
Box::new((Always { arg, interval }).into())
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Eventually`] expression.
|
||||
pub fn make_eventually(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Eventually {
|
||||
arg,
|
||||
interval: (..).into(),
|
||||
})
|
||||
Box::new(
|
||||
(Eventually {
|
||||
arg,
|
||||
interval: (..).into(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Eventually`] expression with an interval.
|
||||
pub fn make_timed_eventually(&self, interval: Interval, arg: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Eventually { arg, interval })
|
||||
Box::new((Eventually { arg, interval }).into())
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Until`] expression.
|
||||
pub fn make_until(&self, lhs: Box<BoolExpr>, rhs: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Until {
|
||||
lhs,
|
||||
rhs,
|
||||
interval: (..).into(),
|
||||
})
|
||||
Box::new(
|
||||
(Until {
|
||||
lhs,
|
||||
rhs,
|
||||
interval: (..).into(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a [`BoolExpr::Until`] expression with an interval.
|
||||
pub fn make_timed_until(&self, interval: Interval, lhs: Box<BoolExpr>, rhs: Box<BoolExpr>) -> Box<BoolExpr> {
|
||||
Box::new(BoolExpr::Until { lhs, rhs, interval })
|
||||
Box::new((Until { lhs, rhs, interval }).into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -605,12 +366,12 @@ pub mod arbitrary {
|
|||
/// Generate arbitrary numeric expressions
|
||||
pub fn num_expr() -> impl Strategy<Value = Box<NumExpr>> {
|
||||
let leaf = prop_oneof![
|
||||
any::<i64>().prop_map(|val| Box::new(NumExpr::IntLit(val))),
|
||||
any::<u64>().prop_map(|val| Box::new(NumExpr::UIntLit(val))),
|
||||
any::<f64>().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 })),
|
||||
any::<i64>().prop_map(|val| Box::new(IntLit(val).into())),
|
||||
any::<u64>().prop_map(|val| Box::new(UIntLit(val).into())),
|
||||
any::<f64>().prop_map(|val| Box::new(FloatLit(val).into())),
|
||||
"[[:word:]]*".prop_map(|name| Box::new((IntVar { name }).into())),
|
||||
"[[:word:]]*".prop_map(|name| Box::new((UIntVar { name }).into())),
|
||||
"[[:word:]]*".prop_map(|name| Box::new((FloatVar { name }).into())),
|
||||
];
|
||||
|
||||
leaf.prop_recursive(
|
||||
|
|
@ -619,19 +380,25 @@ pub mod arbitrary {
|
|||
10, // We put up to 10 items per collection
|
||||
|inner| {
|
||||
prop_oneof![
|
||||
inner.clone().prop_map(|arg| Box::new(NumExpr::Neg { arg })),
|
||||
inner.clone().prop_map(|arg| Box::new((Neg { arg }).into())),
|
||||
prop::collection::vec(inner.clone(), 0..10).prop_map(|args| {
|
||||
Box::new(NumExpr::Add {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
Box::new(
|
||||
(Add {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}),
|
||||
prop::collection::vec(inner.clone(), 0..10).prop_map(|args| {
|
||||
Box::new(NumExpr::Mul {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
Box::new(
|
||||
(Mul {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}),
|
||||
(inner.clone(), inner)
|
||||
.prop_map(|(dividend, divisor)| { Box::new(NumExpr::Div { dividend, divisor }) })
|
||||
.prop_map(|(dividend, divisor)| { Box::new((Div { dividend, divisor }).into()) })
|
||||
]
|
||||
},
|
||||
)
|
||||
|
|
@ -644,14 +411,14 @@ pub mod arbitrary {
|
|||
let lhs = num_expr();
|
||||
let rhs = num_expr();
|
||||
|
||||
(op, lhs, rhs).prop_map(|(op, lhs, rhs)| Box::new(BoolExpr::Cmp { op, lhs, rhs }))
|
||||
(op, lhs, rhs).prop_map(|(op, lhs, rhs)| Box::new((Cmp { op, lhs, rhs }).into()))
|
||||
}
|
||||
|
||||
/// Generate arbitrary boolean expressions
|
||||
pub fn bool_expr() -> impl Strategy<Value = Box<BoolExpr>> {
|
||||
let leaf = prop_oneof![
|
||||
any::<bool>().prop_map(|val| Box::new(BoolExpr::BoolLit(val))),
|
||||
"[[:word:]]*".prop_map(|name| Box::new(BoolExpr::BoolVar { name })),
|
||||
any::<bool>().prop_map(|val| Box::new(BoolLit(val).into())),
|
||||
"[[:word:]]*".prop_map(|name| Box::new((BoolVar { name }).into())),
|
||||
cmp_expr(),
|
||||
];
|
||||
|
||||
|
|
@ -662,27 +429,30 @@ pub mod arbitrary {
|
|||
|inner| {
|
||||
let interval = (any::<(Bound<Duration>, Bound<Duration>)>()).prop_map_into::<Interval>();
|
||||
prop_oneof![
|
||||
inner.clone().prop_map(|arg| Box::new(BoolExpr::Not { arg })),
|
||||
inner.clone().prop_map(|arg| Box::new((Not { arg }).into())),
|
||||
prop::collection::vec(inner.clone(), 0..10).prop_map(|args| {
|
||||
Box::new(BoolExpr::And {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
Box::new(
|
||||
(And {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}),
|
||||
prop::collection::vec(inner.clone(), 0..10).prop_map(|args| {
|
||||
Box::new(BoolExpr::Or {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
Box::new(
|
||||
(Or {
|
||||
args: args.into_iter().map(|arg| *arg).collect(),
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
}),
|
||||
inner.clone().prop_map(|arg| Box::new(BoolExpr::Next { arg })),
|
||||
inner.clone().prop_map(|arg| Box::new((Next { arg }).into())),
|
||||
(inner.clone(), interval.clone())
|
||||
.prop_map(|(arg, interval)| Box::new(BoolExpr::Always { arg, interval })),
|
||||
.prop_map(|(arg, interval)| Box::new((Always { arg, interval }).into())),
|
||||
(inner.clone(), interval.clone())
|
||||
.prop_map(|(arg, interval)| Box::new(BoolExpr::Eventually { arg, interval })),
|
||||
(inner.clone(), inner, interval).prop_map(|(lhs, rhs, interval)| Box::new(BoolExpr::Until {
|
||||
lhs,
|
||||
rhs,
|
||||
interval
|
||||
})),
|
||||
.prop_map(|(arg, interval)| Box::new((Eventually { arg, interval }).into())),
|
||||
(inner.clone(), inner, interval)
|
||||
.prop_map(|(lhs, rhs, interval)| Box::new((Until { lhs, rhs, interval }).into())),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
|
@ -713,8 +483,8 @@ mod tests {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn neg_num_expr(arg in arbitrary::num_expr()) {
|
||||
let expr = -arg;
|
||||
assert!(matches!(expr, NumExpr::Neg { arg: _ }));
|
||||
let expr = -*arg;
|
||||
assert!(matches!(expr, NumExpr::Neg(Neg { arg: _ })));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -724,8 +494,8 @@ mod tests {
|
|||
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: _ }));
|
||||
let expr = *lhs / *rhs;
|
||||
assert!(matches!(expr, NumExpr::$name($name {dividend: _, divisor: _ })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -735,8 +505,8 @@ mod tests {
|
|||
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: _ }));
|
||||
let expr = *lhs $op *rhs;
|
||||
assert!(matches!(expr, NumExpr::$name($name { args: _ })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -750,8 +520,8 @@ mod tests {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn not_bool_expr(arg in arbitrary::bool_expr()) {
|
||||
let expr = !arg;
|
||||
assert!(matches!(expr, BoolExpr::Not { arg: _ }));
|
||||
let expr = !*arg;
|
||||
assert!(matches!(expr, BoolExpr::Not(Not { arg: _ })));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -761,8 +531,8 @@ mod tests {
|
|||
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: _ }));
|
||||
let expr = *lhs $op *rhs;
|
||||
assert!(matches!(expr, BoolExpr::$name($name { args: _ })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
294
argus-core/src/expr/bool_expr.rs
Normal file
294
argus-core/src/expr/bool_expr.rs
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
//! Boolean expression types
|
||||
|
||||
use std::ops::{Bound, RangeBounds};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{BoolExpr, Expr, NumExpr};
|
||||
|
||||
/// Types of comparison operations
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Ordering {
|
||||
/// Equality check for two expressions
|
||||
Eq,
|
||||
/// Non-equality check for two expressions
|
||||
NotEq,
|
||||
/// Less than check
|
||||
Less {
|
||||
/// Denotes `lhs < rhs` if `strict`, and `lhs <= rhs` otherwise.
|
||||
strict: bool,
|
||||
},
|
||||
/// Greater than check
|
||||
Greater {
|
||||
/// Denotes `lhs > rhs` if `strict`, and `lhs >= rhs` otherwise.
|
||||
strict: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Ordering {
|
||||
/// Check if `Ordering::Eq`
|
||||
pub fn equal() -> Self {
|
||||
Self::Eq
|
||||
}
|
||||
|
||||
/// Check if `Ordering::NotEq`
|
||||
pub fn not_equal() -> Self {
|
||||
Self::NotEq
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Less { strict: true }`
|
||||
pub fn less_than() -> Self {
|
||||
Self::Less { strict: true }
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Less { strict: false }`
|
||||
pub fn less_than_eq() -> Self {
|
||||
Self::Less { strict: false }
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Greater { strict: true }`
|
||||
pub fn greater_than() -> Self {
|
||||
Self::Greater { strict: true }
|
||||
}
|
||||
|
||||
/// Check if `Ordering::Less { strict: false }`
|
||||
pub fn greater_than_eq() -> Self {
|
||||
Self::Greater { strict: false }
|
||||
}
|
||||
}
|
||||
|
||||
/// A time interval for a temporal expression.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, derive_more::Into)]
|
||||
#[into(owned, ref, ref_mut)]
|
||||
pub struct Interval {
|
||||
/// Start of the interval
|
||||
pub start: Bound<Duration>,
|
||||
/// End of the interval
|
||||
pub end: Bound<Duration>,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// Create a new interval
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Argus doesn't permit `Interval`s with [`Bound::Excluded(_)`] values (as these
|
||||
/// can't be monitored reliably) and thus converts all such bounds to an
|
||||
/// [`Bound::Included(_)`]. Moreover, if the `start` bound is [`Bound::Unbounded`],
|
||||
/// it will get transformed to [`Bound::Included(Duration::ZERO)`].
|
||||
pub fn new(start: Bound<Duration>, end: Bound<Duration>) -> Self {
|
||||
use Bound::*;
|
||||
let start = match start {
|
||||
a @ Included(_) => a,
|
||||
Excluded(b) => Included(b),
|
||||
Unbounded => Included(Duration::ZERO),
|
||||
};
|
||||
|
||||
let end = match end {
|
||||
Excluded(b) => Included(b),
|
||||
bound => bound,
|
||||
};
|
||||
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
/// Check if the interval is empty
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
use Bound::*;
|
||||
match (&self.start, &self.end) {
|
||||
(Included(a), Included(b)) => a > b,
|
||||
(Included(a), Excluded(b)) | (Excluded(a), Included(b)) | (Excluded(a), Excluded(b)) => a >= b,
|
||||
(Unbounded, Excluded(b)) => b == &Duration::ZERO,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the interval is a singleton
|
||||
///
|
||||
/// This implies that only 1 timepoint is valid within this interval.
|
||||
#[inline]
|
||||
pub fn is_singleton(&self) -> bool {
|
||||
use Bound::*;
|
||||
match (&self.start, &self.end) {
|
||||
(Included(a), Included(b)) => a == b,
|
||||
(Unbounded, Included(b)) => b == &Duration::ZERO,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the interval covers `[0, ..)`.
|
||||
#[inline]
|
||||
pub fn is_untimed(&self) -> bool {
|
||||
use Bound::*;
|
||||
match (self.start, self.end) {
|
||||
(Unbounded, Unbounded) | (Included(Duration::ZERO), Unbounded) => true,
|
||||
(Included(_), Included(_)) | (Included(_), Unbounded) => false,
|
||||
(Excluded(_), _) | (_, Excluded(_)) | (Unbounded, _) => {
|
||||
unreachable!("looks like someone didn't use Interval::new")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Interval
|
||||
where
|
||||
T: RangeBounds<Duration>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value.start_bound().cloned(), value.end_bound().cloned())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(anand): Can I implement this within argus_derive?
|
||||
macro_rules! impl_bool_expr {
|
||||
($ty:ty$(, $($arg:ident),* )? ) => {
|
||||
impl Expr for $ty {
|
||||
fn is_numeric(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_boolean(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<super::ExprRef<'_>> {
|
||||
vec![$($( self.$arg.as_ref().into(), )* )*]
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty, [$args:ident]) => {
|
||||
impl Expr for $ty {
|
||||
fn is_numeric(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_boolean(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<super::ExprRef<'_>> {
|
||||
self.$args.iter().map(|arg| arg.into()).collect()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A `bool` literal
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct BoolLit(pub bool);
|
||||
|
||||
impl_bool_expr!(BoolLit);
|
||||
|
||||
/// A `bool` variable
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct BoolVar {
|
||||
/// Variable name
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl_bool_expr!(BoolVar);
|
||||
|
||||
/// A comparison expression
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Cmp {
|
||||
/// The type of comparison
|
||||
pub op: Ordering,
|
||||
/// The LHS for the comparison
|
||||
pub lhs: Box<NumExpr>,
|
||||
/// The RHS for the comparison
|
||||
pub rhs: Box<NumExpr>,
|
||||
}
|
||||
|
||||
impl_bool_expr!(Cmp, lhs, rhs);
|
||||
|
||||
/// Logical negation of an expression
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Not {
|
||||
/// Expression to be negated
|
||||
pub arg: Box<BoolExpr>,
|
||||
}
|
||||
|
||||
impl_bool_expr!(Not, arg);
|
||||
|
||||
/// Logical conjunction of a list of expressions
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct And {
|
||||
/// Expressions to be "and"-ed
|
||||
pub args: Vec<BoolExpr>,
|
||||
}
|
||||
|
||||
impl_bool_expr!(And, [args]);
|
||||
|
||||
/// Logical disjunction of a list of expressions
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Or {
|
||||
/// Expressions to be "or"-ed
|
||||
pub args: Vec<BoolExpr>,
|
||||
}
|
||||
|
||||
impl_bool_expr!(Or, [args]);
|
||||
|
||||
/// A temporal next expression
|
||||
///
|
||||
/// Checks if the next time point in a signal is `true` or not.
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Next {
|
||||
/// Argument for `Next`
|
||||
pub arg: Box<BoolExpr>,
|
||||
}
|
||||
impl_bool_expr!(Next, arg);
|
||||
|
||||
/// Temporal "oracle" expression
|
||||
///
|
||||
/// This is equivalent to `steps` number of nested [`Next`](BoolExpr::Next)
|
||||
/// expressions.
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Oracle {
|
||||
/// Number of steps to look ahead
|
||||
pub steps: usize,
|
||||
/// Argument for `Oracle`
|
||||
pub arg: Box<BoolExpr>,
|
||||
}
|
||||
impl_bool_expr!(Oracle, arg);
|
||||
|
||||
/// A temporal always expression
|
||||
///
|
||||
/// - If the `interval` is `(Unbounded, Unbounded)` or equivalent to `(0, Unbounded)`:
|
||||
/// checks if the signal is `true` for all points in a signal.
|
||||
/// - Otherwise: checks if the signal is `true` for all points within the `interval`.
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Always {
|
||||
/// Argument for `Always`
|
||||
pub arg: Box<BoolExpr>,
|
||||
/// Interval for the expression
|
||||
pub interval: Interval,
|
||||
}
|
||||
impl_bool_expr!(Always, arg);
|
||||
|
||||
/// A temporal eventually expression
|
||||
///
|
||||
/// - If the `interval` is `(Unbounded, Unbounded)` or equivalent to `(0, Unbounded)`:
|
||||
/// checks if the signal is `true` for some point in a signal.
|
||||
/// - Otherwise: checks if the signal is `true` for some point within the `interval`.
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Eventually {
|
||||
/// Argument for `Eventually`
|
||||
pub arg: Box<BoolExpr>,
|
||||
/// Interval for the expression
|
||||
pub interval: Interval,
|
||||
}
|
||||
impl_bool_expr!(Eventually, arg);
|
||||
|
||||
/// A temporal until expression
|
||||
///
|
||||
/// Checks if the `lhs` is always `true` for a signal until `rhs` becomes `true`.
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::BoolExpr)]
|
||||
pub struct Until {
|
||||
/// LHS to `lhs Until rhs`
|
||||
pub lhs: Box<BoolExpr>,
|
||||
/// RHS to `lhs Until rhs`
|
||||
pub rhs: Box<BoolExpr>,
|
||||
/// Interval for the expression
|
||||
pub interval: Interval,
|
||||
}
|
||||
impl_bool_expr!(Until, lhs, rhs);
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
use std::ops::{BitAnd, BitOr, Not};
|
||||
|
||||
use super::{internal_macros, BoolExpr};
|
||||
|
||||
impl Not for BoolExpr {
|
||||
type Output = BoolExpr;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
BoolExpr::Not { arg: Box::new(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Box<BoolExpr> {
|
||||
type Output = BoolExpr;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
BoolExpr::Not { arg: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for BoolExpr {
|
||||
type Output = BoolExpr;
|
||||
|
||||
#[inline]
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
use BoolExpr::*;
|
||||
|
||||
match (self, rhs) {
|
||||
(Or { args: mut left }, Or { args: mut right }) => {
|
||||
left.append(&mut right);
|
||||
Or { args: left }
|
||||
}
|
||||
(Or { mut args }, other) | (other, Or { mut args }) => {
|
||||
args.push(other);
|
||||
Or { args }
|
||||
}
|
||||
(left, right) => {
|
||||
let args = vec![left, right];
|
||||
Or { args }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal_macros::forward_box_binop! {impl BitOr, bitor for BoolExpr, BoolExpr }
|
||||
|
||||
impl BitAnd for BoolExpr {
|
||||
type Output = BoolExpr;
|
||||
|
||||
#[inline]
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
use BoolExpr::*;
|
||||
|
||||
match (self, rhs) {
|
||||
(And { args: mut left }, And { args: mut right }) => {
|
||||
left.append(&mut right);
|
||||
And { args: left }
|
||||
}
|
||||
(And { mut args }, other) | (other, And { mut args }) => {
|
||||
args.push(other);
|
||||
And { args }
|
||||
}
|
||||
(left, right) => {
|
||||
let args = vec![left, right];
|
||||
And { args }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal_macros::forward_box_binop! {impl BitAnd, bitand for BoolExpr, BoolExpr }
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
macro_rules! forward_box_binop {
|
||||
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
|
||||
impl $imp<$u> for Box<$t> {
|
||||
type Output = <$t as $imp<$u>>::Output;
|
||||
|
||||
#[inline]
|
||||
fn $method(self, other: $u) -> <$t as $imp<$u>>::Output {
|
||||
$imp::$method(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl $imp<Box<$u>> for $t {
|
||||
type Output = <$t as $imp<$u>>::Output;
|
||||
|
||||
#[inline]
|
||||
fn $method(self, other: Box<$u>) -> <$t as $imp<$u>>::Output {
|
||||
$imp::$method(self, *other)
|
||||
}
|
||||
}
|
||||
|
||||
impl $imp<Box<$u>> for Box<$t> {
|
||||
type Output = <$t as $imp<$u>>::Output;
|
||||
|
||||
#[inline]
|
||||
fn $method(self, other: Box<$u>) -> <$t as $imp<$u>>::Output {
|
||||
$imp::$method(*self, *other)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use forward_box_binop;
|
||||
|
|
@ -47,7 +47,7 @@ impl<'a> Iterator for AstIter<'a> {
|
|||
mod tests {
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::expr::{Expr, ExprBuilder, ExprRef};
|
||||
use crate::expr::{ExprBuilder, ExprRef};
|
||||
|
||||
#[test]
|
||||
fn simple_iter() {
|
||||
|
|
|
|||
128
argus-core/src/expr/num_expr.rs
Normal file
128
argus-core/src/expr/num_expr.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
//! Numeric expression types
|
||||
|
||||
use super::{Expr, NumExpr};
|
||||
|
||||
// TODO(anand): Can I implement this within argus_derive?
|
||||
macro_rules! impl_num_expr {
|
||||
($ty:ty$(, $($arg:ident),* )? ) => {
|
||||
impl Expr for $ty {
|
||||
fn is_numeric(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_boolean(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<super::ExprRef<'_>> {
|
||||
vec![$($( self.$arg.as_ref().into(), )* )*]
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty, [$args:ident]) => {
|
||||
impl Expr for $ty {
|
||||
fn is_numeric(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_boolean(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<super::ExprRef<'_>> {
|
||||
self.$args.iter().map(|arg| arg.into()).collect()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A signed integer literal
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct IntLit(pub i64);
|
||||
impl_num_expr!(IntLit);
|
||||
|
||||
/// An unsigned integer literal
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct UIntLit(pub u64);
|
||||
impl_num_expr!(UIntLit);
|
||||
|
||||
/// A floating point literal
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct FloatLit(pub f64);
|
||||
impl_num_expr!(FloatLit);
|
||||
|
||||
/// A signed integer variable
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct IntVar {
|
||||
/// Name of the variable
|
||||
pub name: String,
|
||||
}
|
||||
impl_num_expr!(IntVar);
|
||||
|
||||
/// A unsigned integer variable
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct UIntVar {
|
||||
/// Name of the variable
|
||||
pub name: String,
|
||||
}
|
||||
impl_num_expr!(UIntVar);
|
||||
|
||||
/// A floating point number variable
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct FloatVar {
|
||||
/// Name of the variable
|
||||
pub name: String,
|
||||
}
|
||||
impl_num_expr!(FloatVar);
|
||||
|
||||
/// Numeric negation of a numeric expression
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct Neg {
|
||||
/// Numeric expression being negated
|
||||
pub arg: Box<NumExpr>,
|
||||
}
|
||||
impl_num_expr!(Neg, arg);
|
||||
|
||||
/// Arithmetic addition of a list of numeric expressions
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct Add {
|
||||
/// List of expressions being added
|
||||
pub args: Vec<NumExpr>,
|
||||
}
|
||||
impl_num_expr!(Add, [args]);
|
||||
|
||||
/// Subtraction of two numbers
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct Sub {
|
||||
/// LHS to the expression `lhs - rhs`
|
||||
pub lhs: Box<NumExpr>,
|
||||
/// RHS to the expression `lhs - rhs`
|
||||
pub rhs: Box<NumExpr>,
|
||||
}
|
||||
impl_num_expr!(Sub, lhs, rhs);
|
||||
|
||||
/// Arithmetic multiplication of a list of numeric expressions
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct Mul {
|
||||
/// List of expressions being multiplied
|
||||
pub args: Vec<NumExpr>,
|
||||
}
|
||||
impl_num_expr!(Mul, [args]);
|
||||
|
||||
/// Divide two expressions `dividend / divisor`
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct Div {
|
||||
/// The dividend
|
||||
pub dividend: Box<NumExpr>,
|
||||
/// The divisor
|
||||
pub divisor: Box<NumExpr>,
|
||||
}
|
||||
impl_num_expr!(Div, dividend, divisor);
|
||||
|
||||
/// The absolute value of an expression
|
||||
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
|
||||
pub struct Abs {
|
||||
/// Argument to `abs`
|
||||
pub arg: Box<NumExpr>,
|
||||
}
|
||||
impl_num_expr!(Abs, arg);
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
use std::ops::{Add, Div, Mul, Neg};
|
||||
|
||||
use super::{internal_macros, BoolExpr, NumExpr};
|
||||
|
||||
impl Neg for NumExpr {
|
||||
type Output = NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
NumExpr::Neg { arg: Box::new(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Box<NumExpr> {
|
||||
type Output = NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
NumExpr::Neg { arg: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for NumExpr {
|
||||
type Output = NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
use NumExpr::*;
|
||||
|
||||
match (self, rhs) {
|
||||
(Add { args: mut left }, Add { args: mut right }) => {
|
||||
left.append(&mut right);
|
||||
Add { args: left }
|
||||
}
|
||||
(Add { mut args }, other) | (other, Add { mut args }) => {
|
||||
args.push(other);
|
||||
Add { args }
|
||||
}
|
||||
(left, right) => {
|
||||
let args = vec![left, right];
|
||||
Add { args }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal_macros::forward_box_binop! {impl Add, add for NumExpr, NumExpr }
|
||||
|
||||
impl Mul for NumExpr {
|
||||
type Output = NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
use NumExpr::*;
|
||||
|
||||
match (self, rhs) {
|
||||
(Mul { args: mut left }, Mul { args: mut right }) => {
|
||||
left.append(&mut right);
|
||||
Mul { args: left }
|
||||
}
|
||||
(Mul { mut args }, other) | (other, Mul { mut args }) => {
|
||||
args.push(other);
|
||||
Mul { args }
|
||||
}
|
||||
(left, right) => {
|
||||
let args = vec![left, right];
|
||||
Mul { args }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal_macros::forward_box_binop! {impl Mul, mul for NumExpr, NumExpr }
|
||||
|
||||
impl Div for NumExpr {
|
||||
type Output = NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
use NumExpr::*;
|
||||
Div {
|
||||
dividend: Box::new(self),
|
||||
divisor: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal_macros::forward_box_binop! {impl Div, div for NumExpr, NumExpr }
|
||||
|
||||
use super::Ordering;
|
||||
|
||||
impl NumExpr {
|
||||
/// Convenience method to create an `lhs < rhs` expression.
|
||||
pub fn less_than(self, rhs: Self) -> BoolExpr {
|
||||
BoolExpr::Cmp {
|
||||
op: Ordering::Less { strict: true },
|
||||
lhs: Box::new(self),
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method to create an `lhs <= rhs` expression.
|
||||
pub fn less_than_eq(self, rhs: Self) -> BoolExpr {
|
||||
BoolExpr::Cmp {
|
||||
op: Ordering::Less { strict: false },
|
||||
lhs: Box::new(self),
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method to create an `lhs > rhs` expression.
|
||||
pub fn greater_than(self, rhs: Self) -> BoolExpr {
|
||||
BoolExpr::Cmp {
|
||||
op: Ordering::Greater { strict: true },
|
||||
lhs: Box::new(self),
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method to create an `lhs >= rhs` expression.
|
||||
pub fn greater_than_eq(self, rhs: Self) -> BoolExpr {
|
||||
BoolExpr::Cmp {
|
||||
op: Ordering::Greater { strict: false },
|
||||
lhs: Box::new(self),
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method to create an `lhs == rhs` expression.
|
||||
pub fn equal(self, rhs: Self) -> BoolExpr {
|
||||
BoolExpr::Cmp {
|
||||
op: Ordering::Eq,
|
||||
lhs: Box::new(self),
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method to create an `lhs != rhs` expression.
|
||||
pub fn not_equal(self, rhs: Self) -> BoolExpr {
|
||||
BoolExpr::Cmp {
|
||||
op: Ordering::NotEq,
|
||||
lhs: Box::new(self),
|
||||
rhs: Box::new(rhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use std::any::Any;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
use super::iter::AstIter;
|
||||
use super::ExprRef;
|
||||
use super::{BoolExpr, ExprRef, NumExpr};
|
||||
|
||||
/// A trait representing expressions
|
||||
#[enum_dispatch]
|
||||
pub trait Expr {
|
||||
/// Check if the given expression is a numeric expression
|
||||
fn is_numeric(&self) -> bool;
|
||||
|
|
@ -14,48 +14,10 @@ pub trait Expr {
|
|||
/// If the expression doesn't contain arguments (i.e., it is a leaf expression) then
|
||||
/// the vector is empty.
|
||||
fn args(&self) -> Vec<ExprRef<'_>>;
|
||||
/// Helper function for upcasting to [`std::any::Any`] and then downcasting to a
|
||||
/// concrete [`BoolExpr`](crate::expr::BoolExpr) or
|
||||
/// [`NumExpr`](crate::expr::NumExpr).
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
/// An iterator over the AST starting from the current expression.
|
||||
fn iter(&self) -> AstIter<'_>;
|
||||
}
|
||||
|
||||
impl dyn Expr {
|
||||
/// Convenience method to downcast an expression to a concrete expression node.
|
||||
pub fn downcast_expr_ref<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: Any,
|
||||
{
|
||||
self.as_any().downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
/// Marker trait for numeric expressions
|
||||
pub trait IsNumExpr: Expr + Into<NumExpr> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::super::{arbitrary, BoolExpr, NumExpr};
|
||||
use super::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn downcast_expr_bool(bool_expr in arbitrary::bool_expr()) {
|
||||
let expr_ref = bool_expr.as_ref() as &dyn Expr;
|
||||
|
||||
let downcast_ref = expr_ref.downcast_expr_ref::<BoolExpr>().unwrap();
|
||||
assert_eq!(downcast_ref, bool_expr.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn downcast_expr_num(num_expr in arbitrary::num_expr()) {
|
||||
let expr_ref = num_expr.as_ref() as &dyn Expr;
|
||||
|
||||
let downcast_ref = expr_ref.downcast_expr_ref::<NumExpr>().unwrap();
|
||||
assert_eq!(downcast_ref, num_expr.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Marker trait for Boolean expressions
|
||||
pub trait IsBoolExpr: Expr + Into<BoolExpr> {}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
//! [`enum@Error`]).
|
||||
|
||||
#![warn(missing_docs)]
|
||||
extern crate self as argus_core;
|
||||
|
||||
pub mod expr;
|
||||
pub mod prelude;
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@ pub use cast::*;
|
|||
pub use cmp_ops::*;
|
||||
use itertools::Itertools;
|
||||
pub use num_ops::*;
|
||||
use num_traits::{Num, NumCast};
|
||||
use num_traits::Num;
|
||||
pub use shift_ops::*;
|
||||
pub use traits::*;
|
||||
use utils::intersect_bounds;
|
||||
|
||||
use self::traits::LinearInterpolatable;
|
||||
use crate::{ArgusResult, Error};
|
||||
|
||||
/// Interpolation methods supported by Argus signals.
|
||||
|
|
@ -98,11 +97,13 @@ pub enum Signal<T> {
|
|||
|
||||
impl<T> Signal<T> {
|
||||
/// Create a new empty signal
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::Empty
|
||||
}
|
||||
|
||||
/// Create a new constant signal
|
||||
#[inline]
|
||||
pub fn constant(value: T) -> Self {
|
||||
Self::Constant { value }
|
||||
}
|
||||
|
|
@ -372,7 +373,7 @@ impl<T> Signal<T> {
|
|||
/// Augment synchronization points with time points where signals intersect
|
||||
pub fn sync_with_intersection(&self, other: &Signal<T>) -> Option<Vec<Duration>>
|
||||
where
|
||||
T: PartialOrd + Copy + LinearInterpolatable + NumCast,
|
||||
T: PartialOrd + Copy + LinearInterpolatable,
|
||||
{
|
||||
use core::cmp::Ordering::*;
|
||||
let sync_points: Vec<&Duration> = self.sync_points(other)?.into_iter().collect();
|
||||
|
|
@ -404,13 +405,14 @@ impl<T> Signal<T> {
|
|||
first: other.at(tm1).copied().map(|value| Sample { time: tm1, value }),
|
||||
second: other.at(*t).copied().map(|value| Sample { time: *t, value }),
|
||||
};
|
||||
let intersect = utils::find_intersection(&a, &b);
|
||||
let intersect = T::find_intersection(&a, &b);
|
||||
return_points.push(intersect.time);
|
||||
}
|
||||
}
|
||||
return_points.push(*t);
|
||||
last_sample = Some((*t, ord));
|
||||
}
|
||||
return_points.dedup();
|
||||
return_points.shrink_to_fit();
|
||||
Some(return_points)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use core::iter::zip;
|
||||
use core::time::Duration;
|
||||
|
||||
use itertools::{enumerate, Itertools};
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::traits::LinearInterpolatable;
|
||||
use super::{InterpolationMethod, Signal};
|
||||
|
|
@ -23,7 +23,7 @@ where
|
|||
|
||||
// Find the first index that satisfies `t >= delta` while also checking
|
||||
// if we need to interpolate
|
||||
let Some((idx, first_t)) = time_points.into_iter().find_position(|&t| t >= &delta)
|
||||
let Some((idx, first_t)) = time_points.iter().find_position(|&t| t >= &delta)
|
||||
else {
|
||||
// Return an empty signal (we exhauseted all samples).
|
||||
return Signal::Empty;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use std::time::Duration;
|
|||
|
||||
use paste::paste;
|
||||
|
||||
use super::utils::Neighborhood;
|
||||
use super::{Sample, Signal};
|
||||
use crate::ArgusResult;
|
||||
|
||||
|
|
@ -18,6 +19,12 @@ pub trait LinearInterpolatable {
|
|||
fn interpolate_at(a: &Sample<Self>, b: &Sample<Self>, time: Duration) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Given two signals with two sample points each, find the intersection of the two
|
||||
/// lines.
|
||||
fn find_intersection(a: &Neighborhood<Self>, b: &Neighborhood<Self>) -> Sample<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl LinearInterpolatable for bool {
|
||||
|
|
@ -29,6 +36,45 @@ impl LinearInterpolatable for bool {
|
|||
// We can't linear interpolate a boolean, so we return the previous.
|
||||
a.value
|
||||
}
|
||||
|
||||
fn find_intersection(a: &Neighborhood<Self>, b: &Neighborhood<Self>) -> Sample<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let Sample { time: ta1, value: ya1 } = a.first.unwrap();
|
||||
let Sample { time: ta2, value: ya2 } = a.second.unwrap();
|
||||
let Sample { time: tb1, value: yb1 } = b.first.unwrap();
|
||||
let Sample { time: tb2, value: yb2 } = b.second.unwrap();
|
||||
|
||||
let left_cmp = ya1.cmp(&yb1);
|
||||
let right_cmp = ya2.cmp(&yb2);
|
||||
|
||||
if left_cmp.is_eq() {
|
||||
// They already intersect, so we return the inner time-point
|
||||
if ta1 < tb1 {
|
||||
Sample { time: tb1, value: yb1 }
|
||||
} else {
|
||||
Sample { time: ta1, value: ya1 }
|
||||
}
|
||||
} else if right_cmp.is_eq() {
|
||||
// They intersect at the end, so we return the outer time-point, as that is
|
||||
// when they become equal.
|
||||
if ta2 < tb2 {
|
||||
Sample { time: tb2, value: yb2 }
|
||||
} else {
|
||||
Sample { time: ta2, value: ya2 }
|
||||
}
|
||||
} else {
|
||||
// The switched, so the one that switched earlier will intersect with the
|
||||
// other.
|
||||
// So, we find the one that has a lower time point, i.e., the inner one.
|
||||
if ta2 < tb2 {
|
||||
Sample { time: ta2, value: ya2 }
|
||||
} else {
|
||||
Sample { time: tb2, value: yb2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! interpolate_for_num {
|
||||
|
|
@ -66,6 +112,38 @@ macro_rules! interpolate_for_num {
|
|||
|
||||
cast(val).unwrap()
|
||||
}
|
||||
|
||||
fn find_intersection(a: &Neighborhood<Self>, b: &Neighborhood<Self>) -> Sample<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||
use num_traits::cast;
|
||||
|
||||
let Sample { time: t1, value: y1 } = a.first.unwrap();
|
||||
let Sample { time: t2, value: y2 } = a.second.unwrap();
|
||||
let Sample { time: t3, value: y3 } = b.first.unwrap();
|
||||
let Sample { time: t4, value: y4 } = b.second.unwrap();
|
||||
|
||||
let t1 = t1.as_secs_f64();
|
||||
let t2 = t2.as_secs_f64();
|
||||
let t3 = t3.as_secs_f64();
|
||||
let t4 = t4.as_secs_f64();
|
||||
|
||||
let y1: f64 = cast(y1).unwrap();
|
||||
let y2: f64 = cast(y2).unwrap();
|
||||
let y3: f64 = cast(y3).unwrap();
|
||||
let y4: f64 = cast(y4).unwrap();
|
||||
|
||||
let denom = ((t1 - t2) * (y3 - y4)) - ((y1 - y2) * (t3 - t4));
|
||||
|
||||
let t_top = (((t1 * y2) - (y1 * t2)) * (t3 - t4)) - ((t1 - t2) * (t3 * y4 - y3 * t4));
|
||||
let y_top = (((t1 * y2) - (y1 * t2)) * (y3 - y4)) - ((y1 - y2) * (t3 * y4 - y3 * t4));
|
||||
|
||||
let t = Duration::from_secs_f64(t_top / denom);
|
||||
let y: Self = cast(y_top / denom).unwrap();
|
||||
Sample { time: t, value: y }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ use core::ops::{Bound, RangeBounds};
|
|||
use core::time::Duration;
|
||||
use std::iter::zip;
|
||||
|
||||
use num_traits::NumCast;
|
||||
|
||||
use super::traits::LinearInterpolatable;
|
||||
use super::{InterpolationMethod, Sample, Signal};
|
||||
|
||||
|
|
@ -22,45 +20,11 @@ use super::{InterpolationMethod, Sample, Signal};
|
|||
/// This can be used to interpolate the value at the given `at` time using strategies
|
||||
/// like constant previous, constant following, and linear interpolation.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Neighborhood<T: ?Sized + Copy> {
|
||||
pub struct Neighborhood<T> {
|
||||
pub first: Option<Sample<T>>,
|
||||
pub second: Option<Sample<T>>,
|
||||
}
|
||||
|
||||
/// Given two signals with two sample points each, find the intersection of the two
|
||||
/// lines.
|
||||
pub fn find_intersection<T>(a: &Neighborhood<T>, b: &Neighborhood<T>) -> Sample<T>
|
||||
where
|
||||
T: Copy + NumCast,
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||
use num_traits::cast;
|
||||
|
||||
let Sample { time: t1, value: y1 } = a.first.unwrap();
|
||||
let Sample { time: t2, value: y2 } = a.second.unwrap();
|
||||
let Sample { time: t3, value: y3 } = b.first.unwrap();
|
||||
let Sample { time: t4, value: y4 } = b.second.unwrap();
|
||||
|
||||
let t1 = t1.as_secs_f64();
|
||||
let t2 = t2.as_secs_f64();
|
||||
let t3 = t3.as_secs_f64();
|
||||
let t4 = t4.as_secs_f64();
|
||||
|
||||
let y1: f64 = cast(y1).unwrap();
|
||||
let y2: f64 = cast(y2).unwrap();
|
||||
let y3: f64 = cast(y3).unwrap();
|
||||
let y4: f64 = cast(y4).unwrap();
|
||||
|
||||
let denom = ((t1 - t2) * (y3 - y4)) - ((y1 - y2) * (t3 - t4));
|
||||
|
||||
let t_top = (((t1 * y2) - (y1 * t2)) * (t3 - t4)) - ((t1 - t2) * (t3 * y4 - y3 * t4));
|
||||
let y_top = (((t1 * y2) - (y1 * t2)) * (y3 - y4)) - ((y1 - y2) * (t3 * y4 - y3 * t4));
|
||||
|
||||
let t = Duration::from_secs_f64(t_top / denom);
|
||||
let y: T = cast(y_top / denom).unwrap();
|
||||
Sample { time: t, value: y }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn apply1<T, U, F>(signal: &Signal<T>, op: F) -> Signal<U>
|
||||
where
|
||||
|
|
|
|||
13
argus-derive/Cargo.toml
Normal file
13
argus-derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "argus-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.58"
|
||||
quote = "1.0.27"
|
||||
syn = { version = "2.0.16", features = ["full"] }
|
||||
|
||||
5
argus-derive/README.md
Normal file
5
argus-derive/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# `argus-derive`
|
||||
|
||||
This crate contains [custom derive
|
||||
macros](https://doc.rust-lang.org/reference/procedural-macros.html) for traits described
|
||||
in `argus-core` and `argus-semantics`.
|
||||
5
argus-derive/src/expr.rs
Normal file
5
argus-derive/src/expr.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod bool_expr;
|
||||
mod num_expr;
|
||||
|
||||
pub use bool_expr::*;
|
||||
pub use num_expr::*;
|
||||
93
argus-derive/src/expr/bool_expr.rs
Normal file
93
argus-derive/src/expr/bool_expr.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use proc_macro::{self, TokenStream};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::DeriveInput;
|
||||
|
||||
/// Implement [`IsBoolExpr`](argus_core::expr::traits::IsBoolExpr) and other Boolean
|
||||
/// operations (`Not`, `BitOr`, and `BitAnd`) for the input identifier.
|
||||
pub fn bool_expr_impl(input: DeriveInput) -> TokenStream {
|
||||
let ident = &input.ident;
|
||||
let marker_impl = quote! {
|
||||
impl ::argus_core::expr::traits::IsBoolExpr for #ident {}
|
||||
};
|
||||
|
||||
let not_impl = impl_bool_not(&input);
|
||||
let or_impl = impl_bool_and_or(&input, BoolOp::Or);
|
||||
let and_impl = impl_bool_and_or(&input, BoolOp::And);
|
||||
|
||||
let output = quote! {
|
||||
#marker_impl
|
||||
#not_impl
|
||||
#or_impl
|
||||
#and_impl
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
fn impl_bool_not(input: &DeriveInput) -> impl ToTokens {
|
||||
let ident = &input.ident;
|
||||
quote! {
|
||||
impl ::core::ops::Not for #ident {
|
||||
type Output = ::argus_core::expr::BoolExpr;
|
||||
|
||||
#[inline]
|
||||
fn not(self) -> Self::Output {
|
||||
(::argus_core::expr::Not { arg: Box::new(self.into()) }).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum BoolOp {
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
fn impl_bool_and_or(input: &DeriveInput, op: BoolOp) -> impl ToTokens {
|
||||
let ident = &input.ident;
|
||||
let (trait_fn, trait_name, enum_id) = match op {
|
||||
BoolOp::And => {
|
||||
let trait_name = Ident::new("BitAnd", Span::call_site());
|
||||
let trait_fn = Ident::new("bitand", Span::call_site());
|
||||
let enum_id = Ident::new("And", Span::call_site());
|
||||
(trait_fn, trait_name, enum_id)
|
||||
}
|
||||
BoolOp::Or => {
|
||||
let trait_name = Ident::new("BitOr", Span::call_site());
|
||||
let trait_fn = Ident::new("bitor", Span::call_site());
|
||||
let enum_id = Ident::new("Or", Span::call_site());
|
||||
(trait_fn, trait_name, enum_id)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
impl ::core::ops::#trait_name for #ident {
|
||||
type Output = ::argus_core::expr::BoolExpr;
|
||||
|
||||
#[inline]
|
||||
fn #trait_fn(self, other: Self) -> Self::Output {
|
||||
use ::argus_core::expr::BoolExpr;
|
||||
use ::argus_core::expr::#enum_id;
|
||||
let lhs: BoolExpr = self.into();
|
||||
let rhs: BoolExpr = other.into();
|
||||
|
||||
let expr = match (lhs, rhs) {
|
||||
(BoolExpr::#enum_id(#enum_id { args: mut left }), BoolExpr::#enum_id(#enum_id { args: mut right })) => {
|
||||
left.append(&mut right);
|
||||
#enum_id { args: left }
|
||||
}
|
||||
(BoolExpr::#enum_id(#enum_id { mut args }), other) | (other, BoolExpr::#enum_id(#enum_id { mut args })) => {
|
||||
args.push(other);
|
||||
#enum_id { args }
|
||||
}
|
||||
(left, right) => {
|
||||
let args = vec![left, right];
|
||||
#enum_id { args }
|
||||
}
|
||||
};
|
||||
expr.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
argus-derive/src/expr/num_expr.rs
Normal file
156
argus-derive/src/expr/num_expr.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
use proc_macro::{self, TokenStream};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::DeriveInput;
|
||||
|
||||
/// Implement [`IsNumExpr`](argus_core::expr::traits::IsNumExpr) and other Numean
|
||||
/// operations (`Neg`, `Add`, `Mul`, `Sub`, and `Div`) for the input identifier.
|
||||
pub fn num_expr_impl(input: DeriveInput) -> TokenStream {
|
||||
let ident = &input.ident;
|
||||
let marker_impl = quote! {
|
||||
impl ::argus_core::expr::traits::IsNumExpr for #ident {}
|
||||
};
|
||||
|
||||
let neg_impl = impl_num_neg(&input);
|
||||
let mul_impl = impl_nary_op(&input, NumOp::Mul);
|
||||
let add_impl = impl_nary_op(&input, NumOp::Add);
|
||||
let sub_impl = impl_sub(&input);
|
||||
let div_impl = impl_div(&input);
|
||||
|
||||
let output = quote! {
|
||||
#marker_impl
|
||||
#neg_impl
|
||||
#mul_impl
|
||||
#add_impl
|
||||
#sub_impl
|
||||
#div_impl
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
fn impl_num_neg(input: &DeriveInput) -> impl ToTokens {
|
||||
let ident = &input.ident;
|
||||
quote! {
|
||||
impl ::core::ops::Neg for #ident {
|
||||
type Output = ::argus_core::expr::NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
(::argus_core::expr::Neg { arg: Box::new(self.into()) }).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum NumOp {
|
||||
Add,
|
||||
Mul,
|
||||
}
|
||||
|
||||
impl NumOp {
|
||||
fn get_trait_name(self) -> Ident {
|
||||
match self {
|
||||
NumOp::Add => Ident::new("Add", Span::call_site()),
|
||||
NumOp::Mul => Ident::new("Mul", Span::call_site()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_trait_fn(self) -> Ident {
|
||||
match self {
|
||||
NumOp::Add => Ident::new("add", Span::call_site()),
|
||||
NumOp::Mul => Ident::new("mul", Span::call_site()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr_name(self) -> Ident {
|
||||
match self {
|
||||
NumOp::Add => Ident::new("Add", Span::call_site()),
|
||||
NumOp::Mul => Ident::new("Mul", Span::call_site()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_nary_op(input: &DeriveInput, op: NumOp) -> impl ToTokens {
|
||||
let ident = &input.ident;
|
||||
let trait_name = op.get_trait_name();
|
||||
let trait_fn = op.get_trait_fn();
|
||||
let node_name = op.get_expr_name();
|
||||
quote! {
|
||||
impl<T> ::core::ops::#trait_name<T> for #ident
|
||||
where
|
||||
T: ::core::convert::Into<::argus_core::expr::NumExpr>
|
||||
{
|
||||
type Output = ::argus_core::expr::NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn #trait_fn(self, other: T) -> Self::Output {
|
||||
use ::argus_core::expr::NumExpr;
|
||||
use ::argus_core::expr::#node_name;
|
||||
let lhs: NumExpr = self.into();
|
||||
let rhs: NumExpr = other.into();
|
||||
|
||||
let expr = match (lhs, rhs) {
|
||||
(NumExpr::#node_name(#node_name { args: mut left }), NumExpr::#node_name(#node_name { args: mut right })) => {
|
||||
left.append(&mut right);
|
||||
#node_name { args: left }
|
||||
}
|
||||
(NumExpr::#node_name(#node_name { mut args }), other) | (other, NumExpr::#node_name(#node_name { mut args })) => {
|
||||
args.push(other);
|
||||
#node_name { args }
|
||||
}
|
||||
(left, right) => {
|
||||
let args = vec![left, right];
|
||||
#node_name { args }
|
||||
}
|
||||
};
|
||||
expr.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_sub(input: &DeriveInput) -> impl ToTokens {
|
||||
let ident = &input.ident;
|
||||
quote! {
|
||||
impl<T> ::core::ops::Sub<T> for #ident
|
||||
where
|
||||
T: ::core::convert::Into<::argus_core::expr::NumExpr>
|
||||
{
|
||||
type Output = ::argus_core::expr::NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, other: T) -> Self::Output {
|
||||
use ::argus_core::expr::Sub;
|
||||
let expr = Sub {
|
||||
lhs: Box::new(self.into()),
|
||||
rhs: Box::new(other.into())
|
||||
};
|
||||
expr.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_div(input: &DeriveInput) -> impl ToTokens {
|
||||
let ident = &input.ident;
|
||||
quote! {
|
||||
impl<T> ::core::ops::Div<T> for #ident
|
||||
where
|
||||
T: ::core::convert::Into<::argus_core::expr::NumExpr>
|
||||
{
|
||||
type Output = ::argus_core::expr::NumExpr;
|
||||
|
||||
#[inline]
|
||||
fn div(self, other: T) -> Self::Output {
|
||||
use ::argus_core::expr::Div;
|
||||
let expr = Div {
|
||||
dividend: Box::new(self.into()),
|
||||
divisor: Box::new(other.into())
|
||||
};
|
||||
expr.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
argus-derive/src/lib.rs
Normal file
18
argus-derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use proc_macro::{self, TokenStream};
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod expr;
|
||||
|
||||
use expr::{bool_expr_impl, num_expr_impl};
|
||||
|
||||
#[proc_macro_derive(BoolExpr)]
|
||||
pub fn bool_expr(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input);
|
||||
bool_expr_impl(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(NumExpr)]
|
||||
pub fn num_expr(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input);
|
||||
num_expr_impl(input)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue