refactor!(argus): combine co-dependent crates

- argus-core, argus-parser, argus-semantics are highly co-dependent, and
  hence should be in the same create.
This commit is contained in:
Anand Balakrishnan 2023-10-06 15:40:20 -07:00
parent dc71a51df3
commit 7ce056b471
No known key found for this signature in database
43 changed files with 281 additions and 399 deletions

View file

@ -1,13 +1,5 @@
[workspace]
members = [
"argus",
"argus-core",
"argus-semantics",
"argus-derive",
"argus-parser",
"argus-automata",
"pyargus",
]
members = ["argus", "argus-derive", "argus-automata", "pyargus"]
resolver = "2"

View file

@ -1,27 +0,0 @@
[package]
name = "argus-core"
version = "0.1.1"
authors.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
readme.workspace = true
repository.workspace = true
[dependencies]
derive_more = "0.99.17"
itertools = "0.11"
paste = "1.0.14"
num-traits = "0.2.16"
thiserror = "1.0.47"
proptest = { version = "1.2", optional = true }
enum_dispatch = "0.3.12"
argus-derive = { version = "0.1.0", path = "../argus-derive" }
[dev-dependencies]
argus-core = { path = ".", features = ["arbitrary"] }
[features]
default = []
arbitrary = ["dep:proptest"]

View file

@ -1,23 +0,0 @@
use enum_dispatch::enum_dispatch;
use super::{BoolExpr, ExprRef, NumExpr};
/// A trait representing expressions
#[enum_dispatch]
pub trait AnyExpr {
/// Check if the given expression is a numeric expression
fn is_numeric(&self) -> bool;
/// Check if the given expression is a boolean expression
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<'_>>;
}
/// Marker trait for numeric expressions
pub trait IsNumExpr: AnyExpr + Into<NumExpr> {}
/// Marker trait for Boolean expressions
pub trait IsBoolExpr: AnyExpr + Into<BoolExpr> {}

View file

@ -1,104 +0,0 @@
//! # `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)]
extern crate self as argus_core;
pub mod expr;
pub mod prelude;
pub mod signals;
use std::time::Duration;
pub use expr::*;
pub use signals::Signal;
use thiserror::Error;
/// Errors generated by all Argus components.
#[derive(Error, Debug)]
pub enum Error {
/// An identifier has been redeclared in a specification.
///
/// This is called mainly from [`expr::ExprBuilder`].
#[error("redeclaration of identifier")]
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")]
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")]
InvalidPushToSignal,
/// Pushing the new value to the sampled signal makes it not strictly monotonically
/// increasing.
#[error(
"trying to create a non-monotonically signal, signal end time ({end_time:?}) > sample time point \
({current_sample:?})"
)]
NonMonotonicSignal {
/// The time that the signal actually ends
end_time: Duration,
/// The time point of the new (erroneous) sample.
current_sample: Duration,
},
/// Attempting to perform an invalid operation on a signal
#[error("invalid operation on signal")]
InvalidOperation,
/// Attempting to index a signal not present in a trace.
#[error("name not in signal trace")]
SignalNotPresent,
/// Attempting to perform a signal operation not supported by the type
#[error("incorrect signal type")]
InvalidSignalType,
/// Incorrect cast of signal
#[error("invalid cast from {from} to {to}")]
InvalidCast {
/// Type of the signal being cast from
from: &'static str,
/// Type of the signal being cast to
to: &'static str,
},
/// Invalid interval
#[error("invalid interval: {reason}")]
InvalidInterval {
/// Reason for interval being invalid
reason: &'static str,
},
}
impl Error {
/// An [`InvalidCast`](Error::InvalidCast) error from `T` to `U`.
pub fn invalid_cast<T, U>() -> Self {
Self::InvalidCast {
from: std::any::type_name::<T>(),
to: std::any::type_name::<U>(),
}
}
}
/// Alias for [`Error`](enum@Error)
pub type ArgusError = Error;
/// Alias for [`Result<T, ArgusError>`]
pub type ArgusResult<T> = Result<T, Error>;

View file

@ -1,5 +0,0 @@
#![doc(hidden)]
pub use crate::expr::*;
pub use crate::signals::Signal;
pub use crate::{ArgusError, ArgusResult};

View file

@ -3,12 +3,12 @@ use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::DeriveInput;
/// Implement [`IsBoolExpr`](argus_core::expr::traits::IsBoolExpr) and other Boolean
/// Implement [`IsBoolExpr`](argus::expr::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 {}
impl ::argus::expr::IsBoolExpr for #ident {}
};
let not_impl = impl_bool_not(&input);
@ -29,11 +29,11 @@ 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;
type Output = ::argus::expr::BoolExpr;
#[inline]
fn not(self) -> Self::Output {
(::argus_core::expr::Not { arg: Box::new(self.into()) }).into()
(::argus::expr::Not { arg: Box::new(self.into()) }).into()
}
}
}
@ -62,12 +62,12 @@ fn impl_bool_and_or(input: &DeriveInput, op: BoolOp) -> impl ToTokens {
};
quote! {
impl ::core::ops::#trait_name for #ident {
type Output = ::argus_core::expr::BoolExpr;
type Output = ::argus::expr::BoolExpr;
#[inline]
fn #trait_fn(self, other: Self) -> Self::Output {
use ::argus_core::expr::BoolExpr;
use ::argus_core::expr::#enum_id;
use ::argus::expr::BoolExpr;
use ::argus::expr::#enum_id;
let lhs: BoolExpr = self.into();
let rhs: BoolExpr = other.into();

View file

@ -3,12 +3,12 @@ use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::DeriveInput;
/// Implement [`IsNumExpr`](argus_core::expr::traits::IsNumExpr) and other Numean
/// Implement [`IsNumExpr`](argus::expr::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 {}
impl ::argus::expr::IsNumExpr for #ident {}
};
let neg_impl = impl_num_neg(&input);
@ -33,11 +33,11 @@ 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;
type Output = ::argus::expr::NumExpr;
#[inline]
fn neg(self) -> Self::Output {
(::argus_core::expr::Neg { arg: Box::new(self.into()) }).into()
(::argus::expr::Neg { arg: Box::new(self.into()) }).into()
}
}
}
@ -79,14 +79,14 @@ fn impl_nary_op(input: &DeriveInput, op: NumOp) -> impl ToTokens {
quote! {
impl<T> ::core::ops::#trait_name<T> for #ident
where
T: ::core::convert::Into<::argus_core::expr::NumExpr>
T: ::core::convert::Into<::argus::expr::NumExpr>
{
type Output = ::argus_core::expr::NumExpr;
type Output = ::argus::expr::NumExpr;
#[inline]
fn #trait_fn(self, other: T) -> Self::Output {
use ::argus_core::expr::NumExpr;
use ::argus_core::expr::#node_name;
use ::argus::expr::NumExpr;
use ::argus::expr::#node_name;
let lhs: NumExpr = self.into();
let rhs: NumExpr = other.into();
@ -115,13 +115,13 @@ fn impl_sub(input: &DeriveInput) -> impl ToTokens {
quote! {
impl<T> ::core::ops::Sub<T> for #ident
where
T: ::core::convert::Into<::argus_core::expr::NumExpr>
T: ::core::convert::Into<::argus::expr::NumExpr>
{
type Output = ::argus_core::expr::NumExpr;
type Output = ::argus::expr::NumExpr;
#[inline]
fn sub(self, other: T) -> Self::Output {
use ::argus_core::expr::Sub;
use ::argus::expr::Sub;
let expr = Sub {
lhs: Box::new(self.into()),
rhs: Box::new(other.into())
@ -137,13 +137,13 @@ fn impl_div(input: &DeriveInput) -> impl ToTokens {
quote! {
impl<T> ::core::ops::Div<T> for #ident
where
T: ::core::convert::Into<::argus_core::expr::NumExpr>
T: ::core::convert::Into<::argus::expr::NumExpr>
{
type Output = ::argus_core::expr::NumExpr;
type Output = ::argus::expr::NumExpr;
#[inline]
fn div(self, other: T) -> Self::Output {
use ::argus_core::expr::Div;
use ::argus::expr::Div;
let expr = Div {
dividend: Box::new(self.into()),
divisor: Box::new(other.into())

View file

@ -1,27 +0,0 @@
[package]
name = "argus-parser"
version = "0.1.1"
authors.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
readme.workspace = true
repository.workspace = true
[[example]]
name = "dump_parse_tree"
required-features = ["reporting"]
[[example]]
name = "dump_expr"
required-features = ["reporting"]
[dependencies]
argus-core = { version = "0.1.1", path = "../argus-core" }
ariadne = { version = "0.3.0", optional = true }
chumsky = { version = "1.0.0-alpha.4", features = ["default", "label"] }
[features]
default = []
reporting = ["dep:ariadne"]

View file

@ -1,54 +0,0 @@
use std::env;
use argus_parser::{lexer, parser};
use ariadne::{sources, Color, Label, Report, ReportKind};
use chumsky::prelude::Input;
use chumsky::Parser;
fn main() {
let src = env::args().nth(1).expect("Expected expression");
let (tokens, errs) = lexer().parse(src.as_str()).into_output_errors();
println!("*** Outputting tokens ***");
if let Some(tokens) = &tokens {
for token in tokens {
println!("-> {:?}", token);
}
}
let parse_errs = if let Some(tokens) = &tokens {
let (ast, parse_errs) = parser()
.map_with_span(|ast, span| (ast, span))
.parse(tokens.as_slice().spanned((src.len()..src.len()).into()))
.into_output_errors();
println!("*** Outputting tokens ***");
println!("{:#?}", ast);
parse_errs
} else {
Vec::new()
};
errs.into_iter()
.map(|e| e.map_token(|c| c.to_string()))
.chain(parse_errs.into_iter().map(|e| e.map_token(|tok| tok.to_string())))
.for_each(|e| {
Report::build(ReportKind::Error, src.clone(), e.span().start)
.with_message(e.to_string())
.with_label(
Label::new((src.clone(), e.span().into_range()))
.with_message(e.reason().to_string())
.with_color(Color::Red),
)
.with_labels(e.contexts().map(|(label, span)| {
Label::new((src.clone(), span.into_range()))
.with_message(format!("while parsing this {}", label))
.with_color(Color::Yellow)
}))
.finish()
.print(sources([(src.clone(), src.clone())]))
.unwrap()
});
}

View file

@ -1,20 +0,0 @@
[package]
name = "argus-semantics"
version = "0.1.1"
authors.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
readme.workspace = true
repository.workspace = true
[dependencies]
argus-core = { version = "0.1.1", path = "../argus-core" }
itertools = "0.11"
num-traits = "0.2.16"
paste = "1.0.14"
[dev-dependencies]
argus-core = { path = "../argus-core", features = ["arbitrary"] }
proptest = "1.2"

View file

@ -1,12 +0,0 @@
//! Argus offline semantics
//!
//! In this crate, we are predominantly concerned with the monitoring of _offline system
//! traces_, i.e., a collection of signals that have been extracted from observing and
//! sampling from some system.
pub mod semantics;
pub mod traits;
pub mod utils;
pub use semantics::{BooleanSemantics, QuantitativeSemantics};
pub use traits::Trace;

View file

@ -1,5 +0,0 @@
mod boolean;
mod quantitative;
pub use boolean::BooleanSemantics;
pub use quantitative::QuantitativeSemantics;

View file

@ -9,6 +9,26 @@ rust-version.workspace = true
readme.workspace = true
[dependencies]
argus-core = { version = "0.1.1", path = "../argus-core" }
argus-parser = { version = "0.1.1", path = "../argus-parser" }
argus-semantics = { version = "0.1.1", path = "../argus-semantics" }
argus-derive = { version = "0.1.0", path = "../argus-derive" }
ariadne = { version = "0.3.0", optional = true }
chumsky = { version = "1.0.0-alpha.4", features = ["default", "label"] }
derive_more = "0.99.17"
enum_dispatch = "0.3.12"
itertools = "0.11"
num-traits = "0.2.16"
paste = "1.0.14"
proptest = { version = "1.2", optional = true }
thiserror = "1.0.47"
[dev-dependencies]
proptest = "1.2"
argus = { path = ".", features = ["arbitrary"] }
[features]
default = []
arbitrary = ["dep:proptest"]
reporting = ["dep:ariadne"]
[[example]]
name = "dump_expr"
required-features = ["reporting"]

View file

@ -1,6 +1,6 @@
use std::env;
use argus_parser::parse_str;
use argus::parse_str;
use ariadne::{sources, Color, Label, Report, ReportKind};
fn main() {

View file

@ -15,6 +15,26 @@ pub use traits::*;
use self::iter::AstIter;
use crate::{ArgusResult, Error};
/// A trait representing expressions
#[enum_dispatch]
pub trait AnyExpr {
/// Check if the given expression is a numeric expression
fn is_numeric(&self) -> bool;
/// Check if the given expression is a boolean expression
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<'_>>;
}
/// Marker trait for numeric expressions
pub trait IsNumExpr: AnyExpr + Into<NumExpr> {}
/// Marker trait for Boolean expressions
pub trait IsBoolExpr: AnyExpr + Into<BoolExpr> {}
/// All expressions that are numeric
#[derive(Clone, Debug, PartialEq, argus_derive::NumExpr)]
#[enum_dispatch(AnyExpr)]

View file

@ -0,0 +1 @@

15
argus/src/core/mod.rs Normal file
View file

@ -0,0 +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`]).
pub mod expr;
pub mod signals;
pub use expr::*;
pub use signals::Signal;

View file

@ -1,4 +1,99 @@
pub use argus_core::signals::{AnySignal, Signal};
pub use argus_core::{expr, signals, ArgusResult, Error};
pub use argus_parser::parse_str;
pub use argus_semantics::{BooleanSemantics, QuantitativeSemantics, Trace};
#![warn(missing_docs)]
#![doc = include_str!("../../README.md")]
extern crate self as argus;
mod core;
pub mod parser;
mod semantics;
use std::time::Duration;
use thiserror::Error;
pub use crate::core::signals::{AnySignal, Signal};
pub use crate::core::{expr, signals};
pub use crate::parser::parse_str;
pub use crate::semantics::{BooleanSemantics, QuantitativeSemantics, Trace};
/// Errors generated by all Argus components.
#[derive(Error, Debug)]
pub enum Error {
/// An identifier has been redeclared in a specification.
///
/// This is called mainly from [`expr::ExprBuilder`].
#[error("redeclaration of identifier")]
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")]
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")]
InvalidPushToSignal,
/// Pushing the new value to the sampled signal makes it not strictly monotonically
/// increasing.
#[error(
"trying to create a non-monotonically signal, signal end time ({end_time:?}) > sample time point \
({current_sample:?})"
)]
NonMonotonicSignal {
/// The time that the signal actually ends
end_time: Duration,
/// The time point of the new (erroneous) sample.
current_sample: Duration,
},
/// Attempting to perform an invalid operation on a signal
#[error("invalid operation on signal")]
InvalidOperation,
/// Attempting to index a signal not present in a trace.
#[error("name not in signal trace")]
SignalNotPresent,
/// Attempting to perform a signal operation not supported by the type
#[error("incorrect signal type")]
InvalidSignalType,
/// Incorrect cast of signal
#[error("invalid cast from {from} to {to}")]
InvalidCast {
/// Type of the signal being cast from
from: &'static str,
/// Type of the signal being cast to
to: &'static str,
},
/// Invalid interval
#[error("invalid interval: {reason}")]
InvalidInterval {
/// Reason for interval being invalid
reason: &'static str,
},
}
impl Error {
/// An [`InvalidCast`](Error::InvalidCast) error from `T` to `U`.
pub fn invalid_cast<T, U>() -> Self {
Self::InvalidCast {
from: std::any::type_name::<T>(),
to: std::any::type_name::<U>(),
}
}
}
/// Alias for [`Error`](enum@Error)
pub type ArgusError = Error;
/// Alias for [`Result<T, ArgusError>`]
pub type ArgusResult<T> = Result<T, Error>;

View file

@ -2,9 +2,9 @@ use std::fmt;
use chumsky::prelude::*;
pub type Span = SimpleSpan<usize>;
pub type Output<'a> = Vec<(Token<'a>, Span)>;
pub type Error<'a> = extra::Err<Rich<'a, char, Span>>;
pub(crate) type Span = SimpleSpan<usize>;
pub(crate) type Output<'a> = Vec<(Token<'a>, Span)>;
pub(crate) type Error<'a> = extra::Err<Rich<'a, char, Span>>;
#[derive(Clone, Debug, PartialEq)]
pub enum Token<'src> {

View file

@ -1,16 +1,17 @@
//! # Argus logic syntax
//! # crate::core logic syntax
use std::time::Duration;
use argus_core::ExprBuilder;
use crate::core::expr::ExprBuilder;
mod lexer;
mod parser;
mod syntax;
use chumsky::prelude::Rich;
pub use lexer::{lexer, Error as LexError, Span, Token};
pub use parser::{parser, Expr, Interval};
use lexer::{lexer, Token};
use syntax::{parser, Expr, Interval};
pub fn parse_str(src: &str) -> Result<argus_core::Expr, Vec<Rich<'_, String>>> {
/// Parse a string expression into a concrete Argus expression.
pub fn parse_str(src: &str) -> Result<crate::core::expr::Expr, Vec<Rich<'_, String>>> {
use chumsky::prelude::{Input, Parser};
let (tokens, lex_errors) = lexer().parse(src).into_output_errors();
@ -48,7 +49,7 @@ pub fn parse_str(src: &str) -> Result<argus_core::Expr, Vec<Rich<'_, String>>> {
}
}
fn interval_convert(interval: &Interval<'_>) -> argus_core::Interval {
fn interval_convert(interval: &Interval<'_>) -> crate::core::expr::Interval {
use core::ops::Bound;
let a = if let Some(a) = &interval.a {
match &a.0 {
@ -68,15 +69,15 @@ fn interval_convert(interval: &Interval<'_>) -> argus_core::Interval {
} else {
Bound::Unbounded
};
argus_core::Interval::new(a, b)
crate::core::expr::Interval::new(a, b)
}
/// Convert a parsed [`Expr`] into an [Argus `Expr`](argus_core::Expr)
/// Convert a parsed [`Expr`] into an [crate::core `Expr`](crate::core::expr::Expr)
fn ast_to_expr<'tokens, 'src: 'tokens>(
ast: &Expr<'src>,
span: lexer::Span,
ctx: &mut ExprBuilder,
) -> Result<argus_core::Expr, Rich<'tokens, Token<'src>, lexer::Span>> {
) -> Result<crate::core::expr::Expr, Rich<'tokens, Token<'src>, lexer::Span>> {
match ast {
Expr::Error => unreachable!("Errors should have been caught by parser"),
Expr::Bool(value) => Ok(ctx.bool_const(*value).into()),
@ -84,20 +85,20 @@ fn ast_to_expr<'tokens, 'src: 'tokens>(
Expr::UInt(value) => Ok(ctx.uint_const(*value).into()),
Expr::Float(value) => Ok(ctx.float_const(*value).into()),
Expr::Var { name, kind } => match kind {
parser::Type::Unknown => Err(Rich::custom(span, "All variables must have defined type by now.")),
parser::Type::Bool => ctx
syntax::Type::Unknown => Err(Rich::custom(span, "All variables must have defined type by now.")),
syntax::Type::Bool => ctx
.bool_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
parser::Type::UInt => ctx
syntax::Type::UInt => ctx
.uint_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
parser::Type::Int => ctx
syntax::Type::Int => ctx
.int_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
parser::Type::Float => ctx
syntax::Type::Float => ctx
.float_var(name.to_string())
.map(|var| var.into())
.map_err(|err| Rich::custom(span, err.to_string())),
@ -106,23 +107,23 @@ fn ast_to_expr<'tokens, 'src: 'tokens>(
let arg = ast_to_expr(&arg.0, arg.1, ctx)?;
let interval = interval.as_ref().map(|(i, span)| (interval_convert(i), span));
match op {
parser::UnaryOps::Neg => {
syntax::UnaryOps::Neg => {
assert!(interval.is_none());
let argus_core::Expr::Num(arg) = arg else {
let crate::core::expr::Expr::Num(arg) = arg else {
unreachable!("- must have numeric expression argument");
};
Ok(ctx.make_neg(Box::new(arg)).into())
}
parser::UnaryOps::Not => {
syntax::UnaryOps::Not => {
assert!(interval.is_none());
let argus_core::Expr::Bool(arg) = arg else {
let crate::core::expr::Expr::Bool(arg) = arg else {
unreachable!("`Not` must have boolean expression argument");
};
Ok(ctx.make_not(Box::new(arg)).into())
}
parser::UnaryOps::Next => {
syntax::UnaryOps::Next => {
use core::ops::Bound;
let argus_core::Expr::Bool(arg) = arg else {
let crate::core::expr::Expr::Bool(arg) = arg else {
unreachable!("`Next` must have boolean expression argument");
};
match interval {
@ -141,8 +142,8 @@ fn ast_to_expr<'tokens, 'src: 'tokens>(
None => Ok(ctx.make_next(Box::new(arg)).into()),
}
}
parser::UnaryOps::Always => {
let argus_core::Expr::Bool(arg) = arg else {
syntax::UnaryOps::Always => {
let crate::core::expr::Expr::Bool(arg) = arg else {
unreachable!("`Always` must have boolean expression argument");
};
match interval {
@ -150,8 +151,8 @@ fn ast_to_expr<'tokens, 'src: 'tokens>(
None => Ok(ctx.make_always(Box::new(arg)).into()),
}
}
parser::UnaryOps::Eventually => {
let argus_core::Expr::Bool(arg) = arg else {
syntax::UnaryOps::Eventually => {
let crate::core::expr::Expr::Bool(arg) = arg else {
unreachable!("`Eventually` must have boolean expression argument");
};
match interval {
@ -171,127 +172,127 @@ fn ast_to_expr<'tokens, 'src: 'tokens>(
let interval = interval.as_ref().map(|(i, span)| (interval_convert(i), span));
match op {
parser::BinaryOps::Add => {
syntax::BinaryOps::Add => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Add` must have numeric expression arguments");
};
ctx.make_add([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Sub => {
syntax::BinaryOps::Sub => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Sub` must have numeric expression arguments");
};
Ok(ctx.make_sub(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Mul => {
syntax::BinaryOps::Mul => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Mul` must have numeric expression arguments");
};
ctx.make_mul([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Div => {
syntax::BinaryOps::Div => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("`Div` must have numeric expression arguments");
};
Ok(ctx.make_div(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Lt => {
syntax::BinaryOps::Lt => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_lt(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Le => {
syntax::BinaryOps::Le => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_le(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Gt => {
syntax::BinaryOps::Gt => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_gt(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Ge => {
syntax::BinaryOps::Ge => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_ge(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Eq => {
syntax::BinaryOps::Eq => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_eq(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::Neq => {
syntax::BinaryOps::Neq => {
assert!(interval.is_none());
let (argus_core::Expr::Num(lhs), argus_core::Expr::Num(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Num(lhs), crate::core::expr::Expr::Num(rhs)) = (lhs, rhs) else {
unreachable!("Relational operation must have numeric expression arguments");
};
Ok(ctx.make_neq(Box::new(lhs), Box::new(rhs)).into())
}
parser::BinaryOps::And => {
syntax::BinaryOps::And => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Bool(lhs), crate::core::expr::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`And` must have boolean expression arguments");
};
ctx.make_and([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Or => {
syntax::BinaryOps::Or => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Bool(lhs), crate::core::expr::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Or` must have boolean expression arguments");
};
ctx.make_or([lhs, rhs])
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Implies => {
syntax::BinaryOps::Implies => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Bool(lhs), crate::core::expr::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Implies` must have boolean expression arguments");
};
ctx.make_implies(Box::new(lhs), Box::new(rhs))
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Equiv => {
syntax::BinaryOps::Equiv => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Bool(lhs), crate::core::expr::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Equiv` must have boolean expression arguments");
};
ctx.make_equiv(Box::new(lhs), Box::new(rhs))
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Xor => {
syntax::BinaryOps::Xor => {
assert!(interval.is_none());
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
let (crate::core::expr::Expr::Bool(lhs), crate::core::expr::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Xor` must have boolean expression arguments");
};
ctx.make_xor(Box::new(lhs), Box::new(rhs))
.map(|ex| ex.into())
.map_err(|err| Rich::custom(span, err.to_string()))
}
parser::BinaryOps::Until => {
let (argus_core::Expr::Bool(lhs), argus_core::Expr::Bool(rhs)) = (lhs, rhs) else {
syntax::BinaryOps::Until => {
let (crate::core::expr::Expr::Bool(lhs), crate::core::expr::Expr::Bool(rhs)) = (lhs, rhs) else {
unreachable!("`Until` must have boolean expression arguments");
};
match interval {

View file

@ -2,7 +2,7 @@ use chumsky::input::SpannedInput;
use chumsky::prelude::*;
use chumsky::Parser;
use crate::lexer::{Span, Token};
use super::lexer::{Span, Token};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Type {

View file

@ -1,17 +1,18 @@
use std::ops::Bound;
use std::time::Duration;
use argus_core::expr::*;
use argus_core::prelude::*;
use argus_core::signals::{InterpolationMethod, SignalPartialOrd};
use super::utils::lemire_minmax::MonoWedge;
use super::Trace;
use crate::core::expr::*;
use crate::core::signals::{InterpolationMethod, SignalPartialOrd};
use crate::semantics::QuantitativeSemantics;
use crate::traits::Trace;
use crate::utils::lemire_minmax::MonoWedge;
use crate::{ArgusError, ArgusResult, Signal};
/// Boolean semantics for Signal Temporal Logic expressionsd define by an [`Expr`].
pub struct BooleanSemantics;
impl BooleanSemantics {
/// Evaluates a [Boolean expression](BoolExpr) given a [`Trace`].
pub fn eval<BoolI, NumI>(expr: &BoolExpr, trace: &impl Trace) -> ArgusResult<Signal<bool>>
where
BoolI: InterpolationMethod<bool>,
@ -24,7 +25,7 @@ impl BooleanSemantics {
.ok_or(ArgusError::SignalNotPresent)?
.clone(),
BoolExpr::Cmp(Cmp { op, lhs, rhs }) => {
use argus_core::expr::Ordering::*;
use crate::core::expr::Ordering::*;
let lhs = QuantitativeSemantics::eval_num_expr::<f64, NumI>(lhs, trace)?;
let rhs = QuantitativeSemantics::eval_num_expr::<f64, NumI>(rhs, trace)?;
@ -374,12 +375,12 @@ fn compute_untimed_until<I: InterpolationMethod<bool>>(
mod tests {
use std::collections::HashMap;
use argus_core::expr::ExprBuilder;
use argus_core::signals::interpolation::Linear;
use argus_core::signals::AnySignal;
use itertools::assert_equal;
use super::*;
use crate::core::expr::ExprBuilder;
use crate::core::signals::interpolation::Linear;
use crate::core::signals::AnySignal;
#[derive(Default)]
struct MyTrace {

View file

@ -1,6 +1,17 @@
//! Traits to define semantics for temporal logic specifications
//! Argus offline semantics
//!
//! In this crate, we are predominantly concerned with the monitoring of _offline system
//! traces_, i.e., a collection of signals that have been extracted from observing and
//! sampling from some system.
use argus_core::prelude::*;
mod boolean;
mod quantitative;
pub mod utils;
pub use boolean::BooleanSemantics;
pub use quantitative::QuantitativeSemantics;
use crate::Signal;
/// A trace is a collection of signals
///
@ -9,8 +20,7 @@ use argus_core::prelude::*;
/// An example of a `Trace` may be:
///
/// ```rust
/// use argus_core::signals::{Signal, AnySignal};
/// use argus_semantics::Trace;
/// use argus::{Signal, AnySignal, Trace};
///
/// struct MyTrace {
/// x: Signal<bool>,

View file

@ -1,17 +1,19 @@
use std::ops::Bound;
use std::time::Duration;
use argus_core::expr::*;
use argus_core::prelude::*;
use argus_core::signals::{InterpolationMethod, SignalAbs};
use num_traits::{Num, NumCast};
use crate::traits::Trace;
use crate::utils::lemire_minmax::MonoWedge;
use super::utils::lemire_minmax::MonoWedge;
use super::Trace;
use crate::core::expr::*;
use crate::core::signals::{InterpolationMethod, SignalAbs};
use crate::{ArgusError, ArgusResult, Signal};
/// Quantitative semantics for Signal Temporal Logic expressionsd define by an [`Expr`].
pub struct QuantitativeSemantics;
impl QuantitativeSemantics {
/// Evaluates a [Boolean expression](BoolExpr) given a [`Trace`].
pub fn eval<I>(expr: &BoolExpr, trace: &impl Trace) -> ArgusResult<Signal<f64>>
where
I: InterpolationMethod<f64>,
@ -23,7 +25,7 @@ impl QuantitativeSemantics {
.ok_or(ArgusError::SignalNotPresent)
.map(top_or_bot)?,
BoolExpr::Cmp(Cmp { op, lhs, rhs }) => {
use argus_core::expr::Ordering::*;
use crate::core::expr::Ordering::*;
let lhs = Self::eval_num_expr::<f64, I>(lhs, trace)?;
let rhs = Self::eval_num_expr::<f64, I>(rhs, trace)?;
@ -80,6 +82,7 @@ impl QuantitativeSemantics {
Ok(ret)
}
/// Evaluates a [numeric expression](NumExpr) given a [`Trace`].
pub fn eval_num_expr<T, I>(root: &NumExpr, trace: &impl Trace) -> ArgusResult<Signal<T>>
where
T: Num + NumCast + Clone + PartialOrd,
@ -434,12 +437,12 @@ mod tests {
use std::iter::zip;
use std::time::Duration;
use argus_core::expr::ExprBuilder;
use argus_core::signals::interpolation::{Constant, Linear};
use argus_core::signals::AnySignal;
use itertools::assert_equal;
use super::*;
use crate::core::expr::ExprBuilder;
use crate::core::signals::interpolation::{Constant, Linear};
use crate::core::signals::AnySignal;
const FLOAT_EPS: f64 = 1.0e-8;

View file

View file

@ -69,6 +69,7 @@ impl<'a, T> MonoWedge<'a, T>
where
T: PartialOrd,
{
#[allow(dead_code)]
pub fn min_wedge(duration: Duration) -> Self {
Self::new(duration, T::lt)
}