use std::fmt;
use crate::lexer::duration::Duration;
use crate::parser::aggregation::Grouping;
use crate::parser::selector::{AtModifier, MatrixSelector, VectorSelector};
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Number(f64),
String(String),
VectorSelector(VectorSelector),
MatrixSelector(MatrixSelector),
Call(Call),
Aggregation(Box<Aggregation>),
Binary(Box<BinaryExpr>),
Unary(Box<UnaryExpr>),
Paren(Box<Expr>),
Subquery(Box<SubqueryExpr>),
}
impl Expr {
pub fn is_scalar(&self) -> bool {
matches!(self, Expr::Number(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Expr::String(_))
}
pub fn is_instant_vector(&self) -> bool {
matches!(
self,
Expr::VectorSelector(_)
| Expr::Call(_)
| Expr::Aggregation(_)
| Expr::Binary(_)
| Expr::Unary(_)
) || matches!(self, Expr::Paren(e) if e.is_instant_vector())
}
pub fn is_range_vector(&self) -> bool {
matches!(self, Expr::MatrixSelector(_) | Expr::Subquery(_))
}
pub fn unwrap_parens(&self) -> &Expr {
match self {
Expr::Paren(inner) => inner.unwrap_parens(),
other => other,
}
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expr::Number(n) => {
if n.is_nan() {
write!(f, "NaN")
} else if n.is_infinite() {
if *n > 0.0 {
write!(f, "Inf")
} else {
write!(f, "-Inf")
}
} else {
write!(f, "{}", n)
}
}
Expr::String(s) => write!(f, "\"{}\"", s.escape_default()),
Expr::VectorSelector(v) => write!(f, "{}", v),
Expr::MatrixSelector(m) => write!(f, "{}", m),
Expr::Call(c) => write!(f, "{}", c),
Expr::Aggregation(a) => write!(f, "{}", a),
Expr::Binary(b) => write!(f, "{}", b),
Expr::Unary(u) => write!(f, "{}", u),
Expr::Paren(e) => write!(f, "({})", e),
Expr::Subquery(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Call {
pub name: String,
pub args: Vec<Expr>,
}
impl Call {
pub fn new(name: impl Into<String>, args: Vec<Expr>) -> Self {
Self {
name: name.into(),
args,
}
}
}
impl fmt::Display for Call {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.name)?;
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Aggregation {
pub op: String,
pub expr: Expr,
pub param: Option<Expr>,
pub grouping: Option<Grouping>,
}
impl Aggregation {
pub fn new(op: impl Into<String>, expr: Expr) -> Self {
Self {
op: op.into(),
expr,
param: None,
grouping: None,
}
}
pub fn with_param(op: impl Into<String>, param: Expr, expr: Expr) -> Self {
Self {
op: op.into(),
expr,
param: Some(param),
grouping: None,
}
}
pub fn with_grouping(mut self, grouping: Grouping) -> Self {
self.grouping = Some(grouping);
self
}
}
impl fmt::Display for Aggregation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.op)?;
if let Some(ref grouping) = self.grouping {
write!(f, " {} ", grouping)?;
}
write!(f, "(")?;
if let Some(ref param) = self.param {
write!(f, "{}, ", param)?;
}
write!(f, "{})", self.expr)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add, Sub, Mul, Div, Mod, Pow, Atan2,
Eq, Ne, Lt, Le, Gt, Ge,
And, Or, Unless, }
impl BinaryOp {
pub fn as_str(&self) -> &'static str {
match self {
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
BinaryOp::Pow => "^",
BinaryOp::Atan2 => "atan2",
BinaryOp::Eq => "==",
BinaryOp::Ne => "!=",
BinaryOp::Lt => "<",
BinaryOp::Le => "<=",
BinaryOp::Gt => ">",
BinaryOp::Ge => ">=",
BinaryOp::And => "and",
BinaryOp::Or => "or",
BinaryOp::Unless => "unless",
}
}
pub fn precedence(&self) -> u8 {
match self {
BinaryOp::Or => 1, BinaryOp::And | BinaryOp::Unless => 2, BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Le
| BinaryOp::Gt
| BinaryOp::Ge => 3, BinaryOp::Add | BinaryOp::Sub => 4, BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod | BinaryOp::Atan2 => 5, BinaryOp::Pow => 6, }
}
pub fn is_right_associative(&self) -> bool {
matches!(self, BinaryOp::Pow)
}
pub fn is_comparison(&self) -> bool {
matches!(
self,
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge
)
}
pub fn is_set_operator(&self) -> bool {
matches!(self, BinaryOp::And | BinaryOp::Or | BinaryOp::Unless)
}
pub fn is_arithmetic(&self) -> bool {
matches!(
self,
BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
| BinaryOp::Mod
| BinaryOp::Pow
| BinaryOp::Atan2
)
}
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorMatchingOp {
On, Ignoring, }
impl fmt::Display for VectorMatchingOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VectorMatchingOp::On => write!(f, "on"),
VectorMatchingOp::Ignoring => write!(f, "ignoring"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupSide {
Left, Right, }
impl fmt::Display for GroupSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GroupSide::Left => write!(f, "group_left"),
GroupSide::Right => write!(f, "group_right"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroupModifier {
pub side: GroupSide,
pub labels: Vec<String>,
}
impl fmt::Display for GroupModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.side)?;
if !self.labels.is_empty() {
write!(f, " (")?;
for (i, label) in self.labels.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", label)?;
}
write!(f, ")")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VectorMatching {
pub op: VectorMatchingOp,
pub labels: Vec<String>,
pub group: Option<GroupModifier>,
}
impl fmt::Display for VectorMatching {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (", self.op)?;
for (i, label) in self.labels.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", label)?;
}
write!(f, ")")?;
if let Some(ref group) = self.group {
write!(f, " {}", group)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct BinaryModifier {
pub return_bool: bool,
pub matching: Option<VectorMatching>,
}
impl BinaryModifier {
pub fn with_bool() -> Self {
Self {
return_bool: true,
matching: None,
}
}
pub fn with_matching(matching: VectorMatching) -> Self {
Self {
return_bool: false,
matching: Some(matching),
}
}
pub fn is_empty(&self) -> bool {
!self.return_bool && self.matching.is_none()
}
}
impl fmt::Display for BinaryModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.return_bool {
write!(f, "bool")?;
if self.matching.is_some() {
write!(f, " ")?;
}
}
if let Some(ref matching) = self.matching {
write!(f, "{}", matching)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BinaryExpr {
pub op: BinaryOp,
pub lhs: Expr,
pub rhs: Expr,
pub modifier: Option<BinaryModifier>,
}
impl BinaryExpr {
pub fn new(op: BinaryOp, lhs: Expr, rhs: Expr) -> Self {
Self {
op,
lhs,
rhs,
modifier: None,
}
}
pub fn with_modifier(op: BinaryOp, lhs: Expr, rhs: Expr, modifier: BinaryModifier) -> Self {
Self {
op,
lhs,
rhs,
modifier: Some(modifier),
}
}
}
impl fmt::Display for BinaryExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.lhs, self.op)?;
if let Some(ref modifier) = self.modifier
&& !modifier.is_empty()
{
write!(f, " {}", modifier)?;
}
write!(f, " {}", self.rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Plus,
Minus,
}
impl UnaryOp {
pub fn as_str(&self) -> &'static str {
match self {
UnaryOp::Plus => "+",
UnaryOp::Minus => "-",
}
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnaryExpr {
pub op: UnaryOp,
pub expr: Expr,
}
impl UnaryExpr {
pub fn new(op: UnaryOp, expr: Expr) -> Self {
Self { op, expr }
}
}
impl fmt::Display for UnaryExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.op, self.expr)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SubqueryExpr {
pub expr: Expr,
pub range: Duration,
pub step: Option<Duration>,
pub offset: Option<Duration>,
pub at: Option<AtModifier>,
}
impl SubqueryExpr {
pub fn new(expr: Expr, range: Duration) -> Self {
Self {
expr,
range,
step: None,
offset: None,
at: None,
}
}
pub fn with_step(expr: Expr, range: Duration, step: Duration) -> Self {
Self {
expr,
range,
step: Some(step),
offset: None,
at: None,
}
}
}
impl fmt::Display for SubqueryExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}[{}:", self.expr, self.range)?;
if let Some(ref step) = self.step {
write!(f, "{}", step)?;
}
write!(f, "]")?;
if let Some(ref at) = self.at {
write!(f, " {}", at)?;
}
if let Some(ref offset) = self.offset {
write!(f, " offset {}", offset)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_op_precedence() {
assert!(BinaryOp::Or.precedence() < BinaryOp::And.precedence());
assert!(BinaryOp::And.precedence() < BinaryOp::Eq.precedence());
assert!(BinaryOp::Eq.precedence() < BinaryOp::Add.precedence());
assert!(BinaryOp::Add.precedence() < BinaryOp::Mul.precedence());
assert!(BinaryOp::Mul.precedence() < BinaryOp::Pow.precedence());
}
#[test]
fn test_binary_op_associativity() {
assert!(!BinaryOp::Add.is_right_associative());
assert!(!BinaryOp::Mul.is_right_associative());
assert!(BinaryOp::Pow.is_right_associative());
}
#[test]
fn test_binary_op_categories() {
assert!(BinaryOp::Add.is_arithmetic());
assert!(BinaryOp::Eq.is_comparison());
assert!(BinaryOp::And.is_set_operator());
}
#[test]
fn test_expr_display_number() {
assert_eq!(Expr::Number(42.0).to_string(), "42");
assert_eq!(Expr::Number(3.5).to_string(), "3.5");
assert_eq!(Expr::Number(f64::INFINITY).to_string(), "Inf");
assert_eq!(Expr::Number(f64::NEG_INFINITY).to_string(), "-Inf");
assert_eq!(Expr::Number(f64::NAN).to_string(), "NaN");
}
#[test]
fn test_expr_display_string() {
assert_eq!(Expr::String("hello".to_string()).to_string(), "\"hello\"");
}
#[test]
fn test_unary_expr_display() {
let expr = UnaryExpr::new(UnaryOp::Minus, Expr::Number(42.0));
assert_eq!(expr.to_string(), "-42");
let expr = UnaryExpr::new(UnaryOp::Plus, Expr::Number(42.0));
assert_eq!(expr.to_string(), "+42");
}
#[test]
fn test_binary_expr_display() {
let expr = BinaryExpr::new(BinaryOp::Add, Expr::Number(1.0), Expr::Number(2.0));
assert_eq!(expr.to_string(), "1 + 2");
}
#[test]
fn test_call_display() {
let call = Call::new(
"rate",
vec![Expr::VectorSelector(VectorSelector::new("http_requests"))],
);
assert_eq!(call.to_string(), "rate(http_requests)");
}
#[test]
fn test_aggregation_display() {
let agg = Aggregation::new("sum", Expr::VectorSelector(VectorSelector::new("metric")));
assert_eq!(agg.to_string(), "sum(metric)");
}
#[test]
fn test_expr_is_scalar() {
assert!(Expr::Number(42.0).is_scalar());
assert!(!Expr::String("test".to_string()).is_scalar());
}
#[test]
fn test_expr_unwrap_parens() {
let inner = Expr::Number(42.0);
let paren = Expr::Paren(Box::new(inner.clone()));
let double_paren = Expr::Paren(Box::new(paren.clone()));
assert_eq!(*double_paren.unwrap_parens(), inner);
}
}