docs(core): add documentation for all public API members

This commit is contained in:
Anand Balakrishnan 2023-05-05 14:33:19 -07:00
parent ee75539d73
commit 299e572186
No known key found for this signature in database
9 changed files with 325 additions and 48 deletions

View file

@ -1,3 +1,5 @@
//! Expression tree for Argus specifications
use std::any::Any; use std::any::Any;
use std::collections::HashSet; use std::collections::HashSet;
@ -17,20 +19,61 @@ use crate::{ArgusResult, Error};
/// All expressions that are numeric /// All expressions that are numeric
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum NumExpr { pub enum NumExpr {
/// A signed integer literal
IntLit(i64), IntLit(i64),
/// An unsigned integer literal
UIntLit(u64), UIntLit(u64),
/// A floating point literal
FloatLit(f64), FloatLit(f64),
IntVar { name: String }, /// A signed integer variable
UIntVar { name: String }, IntVar {
FloatVar { name: String }, /// Name of the variable
name: String,
Neg { arg: Box<NumExpr> }, },
Add { args: Vec<NumExpr> }, /// A unsigned integer variable
Sub { lhs: Box<NumExpr>, rhs: Box<NumExpr> }, UIntVar {
Mul { args: Vec<NumExpr> }, /// Name of the variable
Div { dividend: Box<NumExpr>, divisor: Box<NumExpr> }, name: String,
},
Abs { arg: Box<NumExpr> }, /// A floating point number variable
FloatVar {
/// Name of the variable
name: String,
},
/// Numeric negation of a numeric expression
Neg {
/// Numeric expression being negated
arg: Box<NumExpr>,
},
/// Arithmetic addition of a list of numeric expressions
Add {
/// List of expressions being added
args: Vec<NumExpr>,
},
/// Subtraction of two numbers
Sub {
/// LHS to the expression `lhs - rhs`
lhs: Box<NumExpr>,
/// RHS to the expression `lhs - rhs`
rhs: Box<NumExpr>,
},
/// Arithmetic multiplication of a list of numeric expressions
Mul {
/// List of expressions being multiplied
args: Vec<NumExpr>,
},
/// Divide two expressions `dividend / divisor`
Div {
/// The dividend
dividend: Box<NumExpr>,
/// The divisor
divisor: Box<NumExpr>,
},
/// The absolute value of an expression
Abs {
/// Argument to `abs`
arg: Box<NumExpr>,
},
} }
impl Expr for NumExpr { impl Expr for NumExpr {
@ -63,33 +106,49 @@ impl Expr for NumExpr {
/// Types of comparison operations /// Types of comparison operations
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum Ordering { pub enum Ordering {
/// Equality check for two expressions
Eq, Eq,
/// Non-equality check for two expressions
NotEq, NotEq,
Less { strict: bool }, /// Less than check
Greater { strict: bool }, 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 { impl Ordering {
/// Check if `Ordering::Eq`
pub fn equal() -> Self { pub fn equal() -> Self {
Self::Eq Self::Eq
} }
/// Check if `Ordering::NotEq`
pub fn not_equal() -> Self { pub fn not_equal() -> Self {
Self::NotEq Self::NotEq
} }
/// Check if `Ordering::Less { strict: true }`
pub fn less_than() -> Self { pub fn less_than() -> Self {
Self::Less { strict: true } Self::Less { strict: true }
} }
/// Check if `Ordering::Less { strict: false }`
pub fn less_than_eq() -> Self { pub fn less_than_eq() -> Self {
Self::Less { strict: false } Self::Less { strict: false }
} }
/// Check if `Ordering::Greater { strict: true }`
pub fn greater_than() -> Self { pub fn greater_than() -> Self {
Self::Greater { strict: true } Self::Greater { strict: true }
} }
/// Check if `Ordering::Less { strict: false }`
pub fn greater_than_eq() -> Self { pub fn greater_than_eq() -> Self {
Self::Greater { strict: false } Self::Greater { strict: false }
} }
@ -98,17 +157,68 @@ impl Ordering {
/// All expressions that are evaluated to be of type `bool` /// All expressions that are evaluated to be of type `bool`
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum BoolExpr { pub enum BoolExpr {
/// A `bool` literal
BoolLit(bool), BoolLit(bool),
BoolVar { name: String }, /// A `bool` variable
Cmp { op: Ordering, lhs: Box<NumExpr>, rhs: Box<NumExpr> }, BoolVar {
Not { arg: Box<BoolExpr> }, /// Variable name
And { args: Vec<BoolExpr> }, name: String,
Or { args: Vec<BoolExpr> }, },
/// 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>,
},
/// Logical negation of an expression
Not {
/// Expression to be negated
arg: Box<BoolExpr>,
},
/// Logical conjunction of a list of expressions
And {
/// Expressions to be "and"-ed
args: Vec<BoolExpr>,
},
/// Logical disjunction of a list of expressions
Or {
/// Expressions to be "or"-ed
args: Vec<BoolExpr>,
},
Next { arg: Box<BoolExpr> }, /// A temporal next expression
Always { arg: Box<BoolExpr> }, ///
Eventually { arg: Box<BoolExpr> }, /// Checks if the next time point in a signal is `true` or not.
Until { lhs: Box<BoolExpr>, rhs: Box<BoolExpr> }, Next {
/// Argument for `Next`
arg: Box<BoolExpr>,
},
/// A temporal always expression
///
/// Checks if the signal is `true` for all points in a signal.
Always {
/// Argument for `Always`
arg: Box<BoolExpr>,
},
/// A temporal eventually expression
///
/// Checks if the signal is `true` for some point in a signal.
Eventually {
/// Argument for `Eventually`
arg: Box<BoolExpr>,
},
/// 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>,
},
} }
impl Expr for BoolExpr { impl Expr for BoolExpr {
@ -138,9 +248,12 @@ impl Expr for BoolExpr {
} }
} }
/// A reference to an expression (either [`BoolExpr`] or [`NumExpr`]).
#[derive(Clone, Copy, Debug, derive_more::From)] #[derive(Clone, Copy, Debug, derive_more::From)]
pub enum ExprRef<'a> { pub enum ExprRef<'a> {
/// A reference to a [`BoolExpr`]
Bool(&'a BoolExpr), Bool(&'a BoolExpr),
/// A reference to a [`NumExpr`]
Num(&'a NumExpr), Num(&'a NumExpr),
} }
@ -155,28 +268,34 @@ pub struct ExprBuilder {
} }
impl ExprBuilder { impl ExprBuilder {
/// Create a new `ExprBuilder` context.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
declarations: Default::default(), declarations: Default::default(),
} }
} }
/// Declare a constant boolean expression
pub fn bool_const(&self, value: bool) -> Box<BoolExpr> { pub fn bool_const(&self, value: bool) -> Box<BoolExpr> {
Box::new(BoolExpr::BoolLit(value)) Box::new(BoolExpr::BoolLit(value))
} }
/// Declare a constant integer expression
pub fn int_const(&self, value: i64) -> Box<NumExpr> { pub fn int_const(&self, value: i64) -> Box<NumExpr> {
Box::new(NumExpr::IntLit(value)) Box::new(NumExpr::IntLit(value))
} }
/// Declare a constant unsigned integer expression
pub fn uint_const(&self, value: u64) -> Box<NumExpr> { pub fn uint_const(&self, value: u64) -> Box<NumExpr> {
Box::new(NumExpr::UIntLit(value)) Box::new(NumExpr::UIntLit(value))
} }
/// Declare a constant floating point expression
pub fn float_const(&self, value: f64) -> Box<NumExpr> { pub fn float_const(&self, value: f64) -> Box<NumExpr> {
Box::new(NumExpr::FloatLit(value)) Box::new(NumExpr::FloatLit(value))
} }
/// Declare a boolean variable
pub fn bool_var(&mut self, name: String) -> ArgusResult<Box<BoolExpr>> { pub fn bool_var(&mut self, name: String) -> ArgusResult<Box<BoolExpr>> {
if self.declarations.insert(name.clone()) { if self.declarations.insert(name.clone()) {
Ok(Box::new(BoolExpr::BoolVar { name })) Ok(Box::new(BoolExpr::BoolVar { name }))
@ -185,6 +304,7 @@ impl ExprBuilder {
} }
} }
/// Declare a integer variable
pub fn int_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> { pub fn int_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> {
if self.declarations.insert(name.clone()) { if self.declarations.insert(name.clone()) {
Ok(Box::new(NumExpr::IntVar { name })) Ok(Box::new(NumExpr::IntVar { name }))
@ -193,6 +313,7 @@ impl ExprBuilder {
} }
} }
/// Declare a unsigned integer variable
pub fn uint_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> { pub fn uint_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> {
if self.declarations.insert(name.clone()) { if self.declarations.insert(name.clone()) {
Ok(Box::new(NumExpr::UIntVar { name })) Ok(Box::new(NumExpr::UIntVar { name }))
@ -201,6 +322,7 @@ impl ExprBuilder {
} }
} }
/// Declare a floating point variable
pub fn float_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> { pub fn float_var(&mut self, name: String) -> ArgusResult<Box<NumExpr>> {
if self.declarations.insert(name.clone()) { if self.declarations.insert(name.clone()) {
Ok(Box::new(NumExpr::FloatVar { name })) Ok(Box::new(NumExpr::FloatVar { name }))
@ -209,10 +331,12 @@ impl ExprBuilder {
} }
} }
/// Create a [`NumExpr::Neg`] expression
pub fn make_neg(&self, arg: Box<NumExpr>) -> Box<NumExpr> { pub fn make_neg(&self, arg: Box<NumExpr>) -> Box<NumExpr> {
Box::new(NumExpr::Neg { arg }) Box::new(NumExpr::Neg { arg })
} }
/// Create a [`NumExpr::Add`] expression
pub fn make_add<I>(&self, args: I) -> ArgusResult<Box<NumExpr>> pub fn make_add<I>(&self, args: I) -> ArgusResult<Box<NumExpr>>
where where
I: IntoIterator<Item = NumExpr>, I: IntoIterator<Item = NumExpr>,
@ -225,6 +349,7 @@ impl ExprBuilder {
} }
} }
/// Create a [`NumExpr::Mul`] expression
pub fn make_mul<I>(&self, args: I) -> ArgusResult<Box<NumExpr>> pub fn make_mul<I>(&self, args: I) -> ArgusResult<Box<NumExpr>>
where where
I: IntoIterator<Item = NumExpr>, I: IntoIterator<Item = NumExpr>,
@ -237,42 +362,52 @@ impl ExprBuilder {
} }
} }
/// Create a [`NumExpr::Div`] expression
pub fn make_div(&self, dividend: Box<NumExpr>, divisor: Box<NumExpr>) -> Box<NumExpr> { pub fn make_div(&self, dividend: Box<NumExpr>, divisor: Box<NumExpr>) -> Box<NumExpr> {
Box::new(NumExpr::Div { dividend, divisor }) Box::new(NumExpr::Div { dividend, divisor })
} }
/// Create a [`BoolExpr::Cmp`] expression
pub fn make_cmp(&self, op: Ordering, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_cmp(&self, op: Ordering, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
Box::new(BoolExpr::Cmp { op, lhs, rhs }) Box::new(BoolExpr::Cmp { op, lhs, rhs })
} }
/// Create a "less than" ([`BoolExpr::Cmp`]) expression
pub fn make_lt(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_lt(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
self.make_cmp(Ordering::Less { strict: true }, lhs, rhs) self.make_cmp(Ordering::Less { strict: true }, lhs, rhs)
} }
/// Create a "less than or equal" ([`BoolExpr::Cmp`]) expression
pub fn make_le(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_le(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
self.make_cmp(Ordering::Less { strict: false }, lhs, rhs) self.make_cmp(Ordering::Less { strict: false }, lhs, rhs)
} }
/// Create a "greater than" ([`BoolExpr::Cmp`]) expression
pub fn make_gt(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_gt(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
self.make_cmp(Ordering::Greater { strict: true }, lhs, rhs) self.make_cmp(Ordering::Greater { strict: true }, lhs, rhs)
} }
/// Create a "greater than or equal" ([`BoolExpr::Cmp`]) expression
pub fn make_ge(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_ge(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
self.make_cmp(Ordering::Greater { strict: false }, lhs, rhs) self.make_cmp(Ordering::Greater { strict: false }, lhs, rhs)
} }
/// Create a "equals" ([`BoolExpr::Cmp`]) expression
pub fn make_eq(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_eq(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
self.make_cmp(Ordering::Eq, lhs, rhs) self.make_cmp(Ordering::Eq, lhs, rhs)
} }
/// Create a "not equals" ([`BoolExpr::Cmp`]) expression
pub fn make_neq(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> { pub fn make_neq(&self, lhs: Box<NumExpr>, rhs: Box<NumExpr>) -> Box<BoolExpr> {
self.make_cmp(Ordering::NotEq, lhs, rhs) self.make_cmp(Ordering::NotEq, lhs, rhs)
} }
/// Create a [`BoolExpr::Not`] expression.
pub fn make_not(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> { pub fn make_not(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
Box::new(BoolExpr::Not { arg }) Box::new(BoolExpr::Not { arg })
} }
/// Create a [`BoolExpr::Or`] expression.
pub fn make_or<I>(&self, args: I) -> ArgusResult<Box<BoolExpr>> pub fn make_or<I>(&self, args: I) -> ArgusResult<Box<BoolExpr>>
where where
I: IntoIterator<Item = BoolExpr>, I: IntoIterator<Item = BoolExpr>,
@ -285,6 +420,7 @@ impl ExprBuilder {
} }
} }
/// Create a [`BoolExpr::And`] expression.
pub fn make_and<I>(&self, args: I) -> ArgusResult<Box<BoolExpr>> pub fn make_and<I>(&self, args: I) -> ArgusResult<Box<BoolExpr>>
where where
I: IntoIterator<Item = BoolExpr>, I: IntoIterator<Item = BoolExpr>,
@ -297,26 +433,35 @@ impl ExprBuilder {
} }
} }
/// Create a [`BoolExpr::Next`] expression.
pub fn make_next(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> { pub fn make_next(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
Box::new(BoolExpr::Next { arg }) Box::new(BoolExpr::Next { arg })
} }
/// Create a [`BoolExpr::Always`] expression.
pub fn make_always(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> { pub fn make_always(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
Box::new(BoolExpr::Always { arg }) Box::new(BoolExpr::Always { arg })
} }
/// Create a [`BoolExpr::Eventually`] expression.
pub fn make_eventually(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> { pub fn make_eventually(&self, arg: Box<BoolExpr>) -> Box<BoolExpr> {
Box::new(BoolExpr::Eventually { arg }) Box::new(BoolExpr::Eventually { arg })
} }
/// Create a [`BoolExpr::Until`] expression.
pub fn make_until(&self, lhs: Box<BoolExpr>, rhs: Box<BoolExpr>) -> Box<BoolExpr> {
Box::new(BoolExpr::Until { lhs, rhs })
}
} }
#[cfg(any(test, feature = "arbitrary"))] #[cfg(any(test, feature = "arbitrary"))]
pub mod arbitrary { pub mod arbitrary {
//! Helper functions to generate arbitrary expressions using [`proptest`]. //! Helper functions to generate arbitrary expressions using [`mod@proptest`].
use proptest::prelude::*; use proptest::prelude::*;
use super::*; use super::*;
/// Generate arbitrary numeric expressions
pub fn num_expr() -> impl Strategy<Value = Box<NumExpr>> { pub fn num_expr() -> impl Strategy<Value = Box<NumExpr>> {
let leaf = prop_oneof![ let leaf = prop_oneof![
any::<i64>().prop_map(|val| Box::new(NumExpr::IntLit(val))), any::<i64>().prop_map(|val| Box::new(NumExpr::IntLit(val))),
@ -351,6 +496,7 @@ pub mod arbitrary {
) )
} }
/// Generate arbitrary comparison expressions
pub fn cmp_expr() -> impl Strategy<Value = Box<BoolExpr>> { pub fn cmp_expr() -> impl Strategy<Value = Box<BoolExpr>> {
use Ordering::*; use Ordering::*;
let op = prop_oneof![Just(Eq), Just(NotEq),]; let op = prop_oneof![Just(Eq), Just(NotEq),];
@ -360,6 +506,7 @@ pub mod arbitrary {
(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(BoolExpr::Cmp { op, lhs, rhs }))
} }
/// Generate arbitrary boolean expressions
pub fn bool_expr() -> impl Strategy<Value = Box<BoolExpr>> { pub fn bool_expr() -> impl Strategy<Value = Box<BoolExpr>> {
let leaf = prop_oneof![ let leaf = prop_oneof![
any::<bool>().prop_map(|val| Box::new(BoolExpr::BoolLit(val))), any::<bool>().prop_map(|val| Box::new(BoolExpr::BoolLit(val))),

View file

@ -1,3 +1,5 @@
//! Iterators for expression trees
use std::collections::VecDeque; use std::collections::VecDeque;
use super::{Expr, ExprRef}; use super::{Expr, ExprRef};

View file

@ -90,6 +90,7 @@ internal_macros::forward_box_binop! {impl Div, div for NumExpr, NumExpr }
use super::Ordering; use super::Ordering;
impl NumExpr { impl NumExpr {
/// Convenience method to create an `lhs < rhs` expression.
pub fn less_than(self, rhs: Self) -> BoolExpr { pub fn less_than(self, rhs: Self) -> BoolExpr {
BoolExpr::Cmp { BoolExpr::Cmp {
op: Ordering::Less { strict: true }, op: Ordering::Less { strict: true },
@ -98,6 +99,7 @@ impl NumExpr {
} }
} }
/// Convenience method to create an `lhs <= rhs` expression.
pub fn less_than_eq(self, rhs: Self) -> BoolExpr { pub fn less_than_eq(self, rhs: Self) -> BoolExpr {
BoolExpr::Cmp { BoolExpr::Cmp {
op: Ordering::Less { strict: false }, op: Ordering::Less { strict: false },
@ -106,6 +108,7 @@ impl NumExpr {
} }
} }
/// Convenience method to create an `lhs > rhs` expression.
pub fn greater_than(self, rhs: Self) -> BoolExpr { pub fn greater_than(self, rhs: Self) -> BoolExpr {
BoolExpr::Cmp { BoolExpr::Cmp {
op: Ordering::Greater { strict: true }, op: Ordering::Greater { strict: true },
@ -114,6 +117,7 @@ impl NumExpr {
} }
} }
/// Convenience method to create an `lhs >= rhs` expression.
pub fn greater_than_eq(self, rhs: Self) -> BoolExpr { pub fn greater_than_eq(self, rhs: Self) -> BoolExpr {
BoolExpr::Cmp { BoolExpr::Cmp {
op: Ordering::Greater { strict: false }, op: Ordering::Greater { strict: false },
@ -122,6 +126,7 @@ impl NumExpr {
} }
} }
/// Convenience method to create an `lhs == rhs` expression.
pub fn equal(self, rhs: Self) -> BoolExpr { pub fn equal(self, rhs: Self) -> BoolExpr {
BoolExpr::Cmp { BoolExpr::Cmp {
op: Ordering::Eq, op: Ordering::Eq,
@ -130,6 +135,7 @@ impl NumExpr {
} }
} }
/// Convenience method to create an `lhs != rhs` expression.
pub fn not_equal(self, rhs: Self) -> BoolExpr { pub fn not_equal(self, rhs: Self) -> BoolExpr {
BoolExpr::Cmp { BoolExpr::Cmp {
op: Ordering::NotEq, op: Ordering::NotEq,

View file

@ -5,17 +5,25 @@ use super::ExprRef;
/// A trait representing expressions /// A trait representing expressions
pub trait Expr { pub trait Expr {
/// Check if the given expression is a numeric expression
fn is_numeric(&self) -> bool; fn is_numeric(&self) -> bool;
/// Check if the given expression is a boolean expression
fn is_boolean(&self) -> bool; fn is_boolean(&self) -> bool;
/// Get the arguments to the current expression.
///
/// If the expression doesn't contain arguments (i.e., it is a leaf expression) then
/// the vector is empty.
fn args(&self) -> Vec<ExprRef<'_>>; 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; fn as_any(&self) -> &dyn Any;
/// An iterator over the AST starting from the current expression.
fn iter(&self) -> AstIter<'_>; fn iter(&self) -> AstIter<'_>;
} }
impl dyn Expr { impl dyn Expr {
/// Convenience method to downcast an expression to a concrete expression node.
pub fn downcast_expr_ref<T>(&self) -> Option<&T> pub fn downcast_expr_ref<T>(&self) -> Option<&T>
where where
T: Any, T: Any,

View file

@ -1,3 +1,15 @@
//! # `argus-core`
//!
//! This crate provides some of the core functionality or interfaces for the other Argus
//! components. Mainly, the crate provides:
//!
//! 1. Expression tree nodes for defining temporal logic specifications (see [`expr`]).
//! 2. Different signal types for generating traces of data (see [`signals`]).
//! 3. A list of possible errors any component in Argus can generate (see
//! [`enum@Error`]).
#![warn(missing_docs)]
pub mod expr; pub mod expr;
pub mod prelude; pub mod prelude;
pub mod signals; pub mod signals;
@ -6,33 +18,68 @@ use std::time::Duration;
use thiserror::Error; use thiserror::Error;
/// Errors generated by all Argus components.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
/// An identifier has been redeclared in a specification.
///
/// This is called mainly from [`expr::ExprBuilder`].
#[error("redeclaration of identifier")] #[error("redeclaration of identifier")]
IdentifierRedeclaration, IdentifierRedeclaration,
/// An expression is provided with an insufficient number of arguments.
///
/// This is called for N-ary expressions:
/// [`NumExpr::Add`](crate::expr::NumExpr::Add),
/// [`NumExpr::Mul`](crate::expr::NumExpr::Mul),
/// [`BoolExpr::And`](crate::expr::BoolExpr::And), and
/// [`BoolExpr::Or`](crate::expr::BoolExpr::Or).
#[error("insufficient number of arguments")] #[error("insufficient number of arguments")]
IncompleteArgs, IncompleteArgs,
/// Attempting to `push` a new sample to a non-sampled signal
/// ([`Signal::Empty`](crate::signals::Signal::Empty) or
/// [`Signal::Constant`](crate::signals::Signal::Constant)).
#[error("cannot push value to non-sampled signal")] #[error("cannot push value to non-sampled signal")]
InvalidPushToSignal, InvalidPushToSignal,
/// Pushing the new value to the sampled signal makes it not strictly monotonically
/// increasing.
#[error( #[error(
"trying to create a non-monotonically signal, signal end time ({end_time:?}) > sample time point \ "trying to create a non-monotonically signal, signal end time ({end_time:?}) > sample time point \
({current_sample:?})" ({current_sample:?})"
)] )]
NonMonotonicSignal { end_time: Duration, current_sample: Duration }, NonMonotonicSignal {
/// The time that the signal actually ends
end_time: Duration,
/// The time point of the new (erroneous) sample.
current_sample: Duration,
},
#[error("invalid operation due to bad type")] /// Attempting to perform an invalid operation on a signal
#[error("invalid operation on signal")]
InvalidOperation, InvalidOperation,
/// Attempting to index a signal not present in a trace (see
/// [`mod@argus_semantics::Trace`].
#[error("name not in signal trace")] #[error("name not in signal trace")]
SignalNotPresent, SignalNotPresent,
/// Attempting to perform a signal operation not supported by the type
#[error("incorrect signal type")] #[error("incorrect signal type")]
InvalidSignalType, InvalidSignalType,
/// Incorrect cast of signal
#[error("invalid cast from {from} to {to}")] #[error("invalid cast from {from} to {to}")]
InvalidCast { from: &'static str, to: &'static str }, InvalidCast {
/// Type of the signal being cast from
from: &'static str,
/// Type of the signal being cast to
to: &'static str,
},
} }
/// Alias for [`Error`](enum@Error)
pub type ArgusError = Error; pub type ArgusError = Error;
/// Alias for [`Result<T, ArgusError>`]
pub type ArgusResult<T> = Result<T, Error>; pub type ArgusResult<T> = Result<T, Error>;

View file

@ -1,7 +1,5 @@
#![doc(hidden)]
pub use crate::expr::{BoolExpr, Expr, ExprBuilder, ExprRef, NumExpr}; pub use crate::expr::{BoolExpr, Expr, ExprBuilder, ExprRef, NumExpr};
pub use crate::signals::bool_ops::*;
pub use crate::signals::cast::*;
pub use crate::signals::cmp_ops::*;
pub use crate::signals::num_ops::*;
pub use crate::signals::Signal; pub use crate::signals::Signal;
pub use crate::{ArgusError, ArgusResult}; pub use crate::{ArgusError, ArgusResult};

View file

@ -1,18 +1,9 @@
//! Concrete signal types //! Argus signals
//! mod bool_ops;
//! In Argus, there are essentially 2 kinds of signals: mod cast;
//! mod cmp_ops;
//! 1. [`Signal<T>`] is a variable length signal with finitely many sampled points. This
//! implies that the signal has a fixed start and end point (both inclusive) and can
//! be iterated over.
//! 2. [`ConstantSignal<T>`] is a signal that maintains a constant value throughtout
//! its domain, and thus, do not require interpolation and extrapolation. Moreover,
//! since they are defined over the entire time domain, they cannot be iterated over.
pub mod bool_ops;
pub mod cast;
pub mod cmp_ops;
pub mod iter; pub mod iter;
pub mod num_ops; mod num_ops;
pub mod traits; pub mod traits;
mod utils; mod utils;
@ -31,9 +22,16 @@ use utils::intersect_bounds;
use self::traits::LinearInterpolatable; use self::traits::LinearInterpolatable;
use crate::{ArgusResult, Error}; use crate::{ArgusResult, Error};
#[derive(Debug, Clone, Copy)] /// Interpolation methods supported by Argus signals.
///
/// Defaults to `Linear` interpolation (which corresponds to constant interpolation for
/// `bool` signals).
#[derive(Debug, Clone, Copy, Default)]
pub enum InterpolationMethod { pub enum InterpolationMethod {
/// Linear interpolation
#[default]
Linear, Linear,
/// Interpolate from the nearest sample point.
Nearest, Nearest,
} }
@ -61,9 +59,12 @@ impl InterpolationMethod {
} }
} }
/// A single sample of a signal.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Sample<T> { pub struct Sample<T> {
/// The time point when this sample was taken.
pub time: Duration, pub time: Duration,
/// The value of the signal at th given sample.
pub value: T, pub value: T,
} }
@ -73,13 +74,22 @@ pub struct Sample<T> {
/// finite set of strictly monotonically increasing time points. /// finite set of strictly monotonically increasing time points.
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub enum Signal<T> { pub enum Signal<T> {
/// An empty signal.
///
/// This is only used in special (usually error) scenarios.
#[default] #[default]
Empty, Empty,
/// A signal that has a constant value for the entire time domain.
Constant { Constant {
/// The value of the signal for all time.
value: T, value: T,
}, },
/// A finite set of signal values sampled at strictly monotonically increasing time
/// points.
Sampled { Sampled {
/// Values of the samples of the signal.
values: Vec<T>, values: Vec<T>,
/// List of time points where the signal is sampled.
time_points: Vec<Duration>, time_points: Vec<Duration>,
}, },
} }
@ -302,6 +312,14 @@ impl<T> Signal<T> {
} }
} }
/// Return the vector of points where the signal is sampled.
///
/// - If the signal is empty ([`Signal::Empty`]), the output is `None`.
/// - If the signal is constant ([`Signal::Constant`]), the output is equivalent to
/// `Some(vec![])` as there is no well defined list of sample points for a
/// constant signal.
/// - Finally, if the signal is sampled ([`Signal::Sampled`]), the output is the
/// list of time points corresponding to the samples in the signal.
pub fn time_points(&self) -> Option<Vec<&Duration>> { pub fn time_points(&self) -> Option<Vec<&Duration>> {
match self { match self {
Signal::Empty => None, Signal::Empty => None,
@ -397,17 +415,33 @@ impl<T> Signal<T> {
} }
impl<T: Num> Signal<T> { impl<T: Num> Signal<T> {
/// Create a constant `0` signal
pub fn zero() -> Self { pub fn zero() -> Self {
Signal::constant(T::zero()) Signal::constant(T::zero())
} }
/// Create a constant `1` signal
pub fn one() -> Self { pub fn one() -> Self {
Signal::constant(T::one()) Signal::constant(T::one())
} }
} }
impl Signal<bool> {
/// Create a constant `true` signal.
pub fn const_true() -> Self {
Signal::constant(true)
}
/// Create a constant `false` signal.
pub fn const_false() -> Self {
Signal::constant(false)
}
}
#[cfg(any(test, feature = "arbitrary"))] #[cfg(any(test, feature = "arbitrary"))]
pub mod arbitrary { pub mod arbitrary {
//! In this module, we use [`mod@proptest`] to define arbitrary generators for
//! different signals.
use proptest::prelude::*; use proptest::prelude::*;
use proptest::sample::SizeRange; use proptest::sample::SizeRange;

View file

@ -1,12 +1,22 @@
//! Signal iterator.
use std::iter::{zip, Zip}; use std::iter::{zip, Zip};
use std::time::Duration; use std::time::Duration;
use super::Signal; use super::Signal;
/// An iterator over a [`Signal`].
///
/// This takes into account if the signal is iterable or not, i.e., it produces samples
/// only for [`Signal::Sampled`] and empty iterators for [`Signal::Empty`] (as it
/// contains no values) and [`Signal::Constant`] (as there is no well defined start and
/// end to the signal).
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub enum Iter<'a, T> { pub enum Iter<'a, T> {
#[doc(hidden)]
#[default] #[default]
Empty, Empty,
#[doc(hidden)]
Iter(Zip<core::slice::Iter<'a, Duration>, core::slice::Iter<'a, T>>), Iter(Zip<core::slice::Iter<'a, Duration>, core::slice::Iter<'a, T>>),
} }

View file

@ -1,3 +1,5 @@
//! Helper traits for Argus signals.
use std::any::Any; use std::any::Any;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::time::Duration; use std::time::Duration;
@ -9,6 +11,10 @@ use crate::ArgusResult;
/// Trait for values that are linear interpolatable /// Trait for values that are linear interpolatable
pub trait LinearInterpolatable { pub trait LinearInterpolatable {
/// Compute the linear interpolation of two samples at `time`
///
/// This should assume that the `time` value is between the sample times of `a` and
/// `b`. This should be enforced as an assertion.
fn interpolate_at(a: &Sample<Self>, b: &Sample<Self>, time: Duration) -> Self fn interpolate_at(a: &Sample<Self>, b: &Sample<Self>, time: Duration) -> Self
where where
Self: Sized; Self: Sized;
@ -80,6 +86,8 @@ interpolate_for_num!(f64);
/// This is mainly for external libraries to use for trait objects and downcasting to /// This is mainly for external libraries to use for trait objects and downcasting to
/// concrete [`Signal`] types. /// concrete [`Signal`] types.
pub trait AnySignal { pub trait AnySignal {
/// Convenience method to upcast a signal to [`std::any::Any`] for later downcasting
/// to a concrete [`Signal`] type.
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
} }
@ -92,6 +100,7 @@ impl<T: 'static> AnySignal for Signal<T> {
macro_rules! impl_signal_cmp { macro_rules! impl_signal_cmp {
($cmp:ident) => { ($cmp:ident) => {
paste! { paste! {
/// Compute the time-wise comparison of two signals
fn [<signal_ $cmp>](&self, other: &Rhs) -> Option<Signal<bool>> { fn [<signal_ $cmp>](&self, other: &Rhs) -> Option<Signal<bool>> {
self.signal_cmp(other, |ord| ord.[<is_ $cmp>]()) self.signal_cmp(other, |ord| ord.[<is_ $cmp>]())
} }
@ -120,6 +129,7 @@ pub trait SignalPartialOrd<Rhs = Self> {
/// Time-wise min-max of signal types /// Time-wise min-max of signal types
pub trait SignalMinMax<Rhs = Self> { pub trait SignalMinMax<Rhs = Self> {
/// The output type of the signal after computing the min/max
type Output; type Output;
/// Compute the time-wise min of two signals /// Compute the time-wise min of two signals
@ -131,24 +141,39 @@ pub trait SignalMinMax<Rhs = Self> {
/// Trait for converting between signal types /// Trait for converting between signal types
pub trait SignalNumCast { pub trait SignalNumCast {
/// Try to convert the signal values/samples to `i8`
fn to_i8(&self) -> Option<Signal<i8>>; fn to_i8(&self) -> Option<Signal<i8>>;
/// Try to convert the signal values/samples to `i16`
fn to_i16(&self) -> Option<Signal<i16>>; fn to_i16(&self) -> Option<Signal<i16>>;
/// Try to convert the signal values/samples to `i32`
fn to_i32(&self) -> Option<Signal<i32>>; fn to_i32(&self) -> Option<Signal<i32>>;
/// Try to convert the signal values/samples to `i64`
fn to_i64(&self) -> Option<Signal<i64>>; fn to_i64(&self) -> Option<Signal<i64>>;
/// Try to convert the signal values/samples to `u8`
fn to_u8(&self) -> Option<Signal<u8>>; fn to_u8(&self) -> Option<Signal<u8>>;
/// Try to convert the signal values/samples to `u16`
fn to_u16(&self) -> Option<Signal<u16>>; fn to_u16(&self) -> Option<Signal<u16>>;
/// Try to convert the signal values/samples to `u32`
fn to_u32(&self) -> Option<Signal<u32>>; fn to_u32(&self) -> Option<Signal<u32>>;
/// Try to convert the signal values/samples to `u64`
fn to_u64(&self) -> Option<Signal<u64>>; fn to_u64(&self) -> Option<Signal<u64>>;
/// Try to convert the signal values/samples to `f32`
fn to_f32(&self) -> Option<Signal<f32>>; fn to_f32(&self) -> Option<Signal<f32>>;
/// Try to convert the signal values/samples to `f64`
fn to_f64(&self) -> Option<Signal<f64>>; fn to_f64(&self) -> Option<Signal<f64>>;
} }
/// Trait to cast signal onto some type /// Trait to cast signal onto some type
pub trait TrySignalCast<T>: Sized + SignalNumCast { pub trait TrySignalCast<T>: Sized + SignalNumCast {
/// Try to cast the given signal to another numeric type.
///
/// This returns a [`ArgusError::InvalidCast`](crate::Error::InvalidCast) if
/// some value in the signal isn't castable to the destination type.
fn try_cast(&self) -> ArgusResult<T>; fn try_cast(&self) -> ArgusResult<T>;
} }
/// Trait for computing the absolute value of the samples in a signal /// Trait for computing the absolute value of the samples in a signal
pub trait SignalAbs { pub trait SignalAbs {
/// Compute the absolute value of the given signal
fn abs(&self) -> Self; fn abs(&self) -> Self;
} }