use vec::{vector, label_name, Vector};
use str::string;
use nom::{
recognize_float,
IResult,
};
use nom::types::CompleteByteSlice;
#[derive(Debug, PartialEq)]
pub enum Op {
Pow(Option<OpMod>),
Mul(Option<OpMod>),
Div(Option<OpMod>),
Mod(Option<OpMod>),
Plus(Option<OpMod>),
Minus(Option<OpMod>),
Eq(bool, Option<OpMod>),
Ne(bool, Option<OpMod>),
Lt(bool, Option<OpMod>),
Gt(bool, Option<OpMod>),
Le(bool, Option<OpMod>),
Ge(bool, Option<OpMod>),
And(Option<OpMod>),
Unless(Option<OpMod>),
Or(Option<OpMod>),
}
#[derive(Debug, PartialEq)]
pub enum OpModAction { RestrictTo, Ignore }
#[derive(Debug, PartialEq)]
pub struct OpMod {
pub action: OpModAction,
pub labels: Vec<String>,
pub group: Option<OpGroupMod>,
}
#[derive(Debug, PartialEq)]
pub enum OpGroupSide { Left, Right }
#[derive(Debug, PartialEq)]
pub struct OpGroupMod {
pub side: OpGroupSide,
pub labels: Vec<String>,
}
#[derive(Debug, PartialEq)]
pub enum AggregationAction { Without, By }
#[derive(Debug, PartialEq)]
pub struct AggregationMod {
pub action: AggregationAction,
pub labels: Vec<String>,
}
#[derive(Debug, PartialEq)]
pub enum Node {
Operator {
x: Box<Node>,
op: Op,
y: Box<Node>
},
Vector(Vector),
Scalar(f32),
String(String),
Function {
name: String,
args: Vec<Node>,
aggregation: Option<AggregationMod>,
},
Negation(Box<Node>),
}
impl Node {
fn operator(x: Node, op: Op, y: Node) -> Node {
Node::Operator {
x: Box::new(x),
op,
y: Box::new(y)
}
}
fn negation(x: Node) -> Node {
Node::Negation(Box::new(x))
}
}
named!(label_list <CompleteByteSlice, Vec<String>>, ws!(delimited!(
char!('('),
separated_list!(char!(','), label_name),
char!(')')
)));
named!(function_aggregation <CompleteByteSlice, AggregationMod>, ws!(do_parse!(
action: alt!(
tag!("by") => { |_| AggregationAction::By }
| tag!("without") => { |_| AggregationAction::Without }
) >>
labels: label_list >>
(AggregationMod { action, labels })
)));
fn function_args(input: CompleteByteSlice, allow_periods: bool) -> IResult<CompleteByteSlice, Vec<Node>> {
ws!(
input,
delimited!(
char!('('),
separated_list!(char!(','), alt!(
call!(expression, allow_periods) => { |e| e }
| string => { |s| Node::String(s) }
)),
char!(')')
)
)
}
fn function(input: CompleteByteSlice, allow_periods: bool) -> IResult<CompleteByteSlice, Node> {
ws!(
input,
do_parse!(
name: label_name >>
args_agg: alt!(
do_parse!(
args: call!(function_args, allow_periods) >>
aggregation: opt!(function_aggregation) >>
((args, aggregation))
)
|
do_parse!(
aggregation: opt!(function_aggregation) >>
args: call!(function_args, allow_periods) >>
((args, aggregation))
)
) >>
({
let (args, aggregation) = args_agg;
Node::Function { name, args, aggregation }
})
)
)
}
fn atom(input: CompleteByteSlice, allow_periods: bool) -> IResult<CompleteByteSlice, Node> {
ws!(
input,
alt!(
map!(tag_no_case!("NaN"), |_| Node::Scalar(::std::f32::NAN))
|
map!(
flat_map!(call!(recognize_float), parse_to!(f32)),
Node::Scalar
)
|
preceded!(char!('+'), call!(atom, allow_periods))
|
map!(preceded!(char!('-'), call!(atom, allow_periods)), |a| Node::negation(a))
|
call!(function, allow_periods)
|
map!(call!(vector, allow_periods), Node::Vector)
|
delimited!(char!('('), call!(expression, allow_periods), char!(')'))
)
)
}
macro_rules! with_modifier {
($i:expr, $literal:expr, $op:expr) => (
map!(
$i,
preceded!(tag!($literal), opt!(op_modifier)),
|op_mod| $op(op_mod)
)
)
}
macro_rules! with_bool_modifier {
($i:expr, $literal:expr, $op:expr) => (
ws!($i, do_parse!(
tag!($literal) >>
boolness: opt!(tag!("bool")) >>
op_mod: opt!(op_modifier) >>
($op(boolness.is_some(), op_mod))
))
)
}
named!(op_modifier <CompleteByteSlice, OpMod>, ws!(do_parse!(
action: alt!(
tag!("on") => { |_| OpModAction::RestrictTo }
| tag!("ignoring") => { |_| OpModAction::Ignore }
) >>
labels: label_list >>
group: opt!(ws!(do_parse!(
side: alt!(
tag!("group_left") => { |_| OpGroupSide::Left }
| tag!("group_right") => { |_| OpGroupSide::Right }
) >>
labels: map!(
opt!(label_list),
|labels| labels.unwrap_or(vec![])
) >>
(OpGroupMod { side, labels })
))) >>
(OpMod { action, labels, group })
)));
fn power(input: CompleteByteSlice, allow_periods: bool) -> IResult<CompleteByteSlice, Node> {
ws!(
input,
do_parse!(
x: call!(atom, allow_periods) >>
y: opt!(tuple!(
with_modifier!("^", Op::Pow),
call!(power, allow_periods)
)) >>
( match y {
None => x,
Some((op, y)) => Node::operator(x, op, y),
} )
)
)
}
macro_rules! left_op {
($name:ident, $next:ident, $op:ident!($($op_args:tt)*)) => (
fn $name(input: CompleteByteSlice, allow_periods: bool) -> IResult<CompleteByteSlice, Node>{
ws!(
input,
do_parse!(
x: call!($next, allow_periods) >>
ops: many0!(tuple!(
$op!($($op_args)*),
call!($next, allow_periods)
)) >>
({
let mut x = x;
for (op, y) in ops {
x = Node::operator(x, op, y);
}
x
})
)
)
}
);
}
left_op!(mul_div_mod, power, alt!(
with_modifier!("*", Op::Mul)
| with_modifier!("/", Op::Div)
| with_modifier!("%", Op::Mod)
));
left_op!(plus_minus, mul_div_mod, alt!(
with_modifier!("+", Op::Plus)
| with_modifier!("-", Op::Minus)
));
left_op!(comparison, plus_minus, alt!(
with_bool_modifier!("==", Op::Eq)
| with_bool_modifier!("!=", Op::Ne)
| with_bool_modifier!("<=", Op::Le)
| with_bool_modifier!(">=", Op::Ge)
| with_bool_modifier!("<", Op::Lt)
| with_bool_modifier!(">", Op::Gt)
));
left_op!(and_unless, comparison, alt!(
with_modifier!("and", Op::And)
| with_modifier!("unless", Op::Unless)
));
left_op!(or_op, and_unless, with_modifier!("or", Op::Or));
pub(crate) fn expression(input: CompleteByteSlice, allow_periods: bool) -> IResult<CompleteByteSlice, Node> {
call!(input, or_op, allow_periods)
}
#[allow(unused_imports)]
#[cfg(test)]
mod tests {
use super::*;
use vec;
use nom::ErrorKind;
use self::Node::{Scalar, Function};
use self::Op::*;
#[allow(non_upper_case_globals)]
const operator: fn(Node, Op, Node) -> Node = Node::operator;
#[allow(non_upper_case_globals)]
const negation: fn(Node) -> Node = Node::negation;
fn vector(expr: &str) -> Node {
match vec::vector(cbs(expr), false) {
Ok((CompleteByteSlice(b""), x)) => Node::Vector(x),
_ => panic!("failed to parse label correctly")
}
}
fn cbs(s: &str) -> CompleteByteSlice {
CompleteByteSlice(s.as_bytes())
}
#[test]
fn scalar() {
scalar_single("123", 123.);
scalar_single("-123", -123.);
scalar_single("123.", 123.);
scalar_single("-123.", -123.);
scalar_single("123.45", 123.45);
scalar_single("-123.45", -123.45);
scalar_single(".123", 0.123);
scalar_single("-.123", -0.123);
scalar_single("123e5", 123e5);
scalar_single("-123e5", -123e5);
scalar_single("1.23e5", 1.23e5);
scalar_single("-1.23e5", -1.23e5);
scalar_single("1.23e-5", 1.23e-5);
scalar_single("-1.23e-5", -1.23e-5);
}
fn scalar_single(input: &str, output: f32) {
assert_eq!(expression(cbs(input), false), Ok((cbs(""), Scalar(output))));
}
#[test]
fn ops() {
assert_eq!(
expression(cbs("foo > bar != 0 and 15.5 < xyzzy"), false),
Ok((cbs(""), operator(
operator(
operator(vector("foo"), Gt(false, None), vector("bar")),
Ne(false, None),
Scalar(0.)
),
And(None),
operator(Scalar(15.5), Lt(false, None), vector("xyzzy")),
)))
);
assert_eq!(
expression(cbs("foo + bar - baz <= quux + xyzzy"), false),
Ok((cbs(""), operator(
operator(
operator(vector("foo"), Plus(None), vector("bar")),
Minus(None),
vector("baz"),
),
Le(false, None),
operator(vector("quux"), Plus(None), vector("xyzzy")),
)))
);
assert_eq!(
expression(cbs("foo + bar % baz"), false),
Ok((cbs(""), operator(
vector("foo"),
Plus(None),
operator(vector("bar"), Mod(None), vector("baz")),
)))
);
assert_eq!(
expression(cbs("x^y^z"), false),
Ok((cbs(""), operator(
vector("x"),
Pow(None),
operator(vector("y"), Pow(None), vector("z")),
)))
);
assert_eq!(
expression(cbs("(a+b)*c"), false),
Ok((cbs(""), operator(
operator(vector("a"), Plus(None), vector("b")),
Mul(None),
vector("c"),
)))
);
}
#[test]
fn op_mods() {
assert_eq!(
expression(cbs("foo + ignoring (instance) bar / on (cluster) baz"), false),
Ok((cbs(""), operator(
vector("foo"),
Plus(Some(OpMod {
action: OpModAction::Ignore,
labels: vec!["instance".to_string()],
group: None,
})),
operator(
vector("bar"),
Div(Some(OpMod {
action: OpModAction::RestrictTo,
labels: vec!["cluster".to_string()],
group: None,
})),
vector("baz"),
)
)))
);
assert_eq!(
expression(cbs("foo + ignoring (instance) group_right bar / on (cluster, shmuster) group_left (job) baz"), false),
Ok((cbs(""), operator(
vector("foo"),
Plus(Some(OpMod {
action: OpModAction::Ignore,
labels: vec!["instance".to_string()],
group: Some(OpGroupMod { side: OpGroupSide::Right, labels: vec![] }),
})),
operator(
vector("bar"),
Div(Some(OpMod {
action: OpModAction::RestrictTo,
labels: vec!["cluster".to_string(), "shmuster".to_string()],
group: Some(OpGroupMod { side: OpGroupSide::Left, labels: vec!["job".to_string()] }),
})),
vector("baz"),
)
)))
);
assert_eq!(
expression(cbs("node_cpu{cpu='cpu0'} > bool ignoring (cpu) node_cpu{cpu='cpu1'}"), false),
Ok((cbs(""), operator(
vector("node_cpu{cpu='cpu0'}"),
Gt(true, Some(OpMod {
action: OpModAction::Ignore,
labels: vec!["cpu".to_string()],
group: None,
})),
vector("node_cpu{cpu='cpu1'}"),
)))
);
}
#[test]
fn unary() {
assert_eq!(
expression(cbs("a + -b"), false),
Ok((cbs(""), operator(
vector("a"),
Plus(None),
negation(vector("b")),
)))
);
assert_eq!(
expression(cbs("a ^ - 1 - b"), false),
Ok((cbs(""), operator(
operator(
vector("a"),
Pow(None),
negation(Scalar(1.)),
),
Minus(None),
vector("b"),
)))
);
assert_eq!(
expression(cbs("a ^ - (1 - b)"), false),
Ok((cbs(""), operator(
vector("a"),
Pow(None),
negation(operator(
Scalar(1.),
Minus(None),
vector("b"),
)),
)))
);
assert_eq!(
expression(cbs("a +++++++ b"), false),
Ok((cbs(""), operator(
vector("a"),
Plus(None),
vector("b"),
)))
);
assert_eq!(
expression(cbs("a * --+-b"), false),
Ok((cbs(""), operator(
vector("a"),
Mul(None),
negation(negation(negation(vector("b")))),
)))
);
}
#[test]
fn functions() {
assert_eq!(
expression(cbs("foo() + bar(baz) + quux(xyzzy, plough)"), false),
Ok((cbs(""), operator(
operator(
Function {
name: "foo".to_string(),
args: vec![],
aggregation: None,
},
Plus(None),
Function {
name: "bar".to_string(),
args: vec![vector("baz")],
aggregation: None,
},
),
Plus(None),
Function {
name: "quux".to_string(),
args: vec![
vector("xyzzy"),
vector("plough"),
],
aggregation: None,
},
)))
);
assert_eq!(
expression(cbs("round(rate(whatever [5m]) > 0, 0.2)"), false),
Ok((cbs(""),
Function {
name: "round".to_string(),
args: vec![
operator(
Function {
name: "rate".to_string(),
args: vec![vector("whatever [5m]")],
aggregation: None,
},
Gt(false, None),
Scalar(0.),
),
Scalar(0.2)
],
aggregation: None,
}
))
);
assert_eq!(
expression(cbs("label_replace(up, 'instance', '', 'instance', '.*')"), false),
Ok((cbs(""), Function {
name: "label_replace".to_string(),
args: vec![
vector("up"),
Node::String("instance".to_string()),
Node::String("".to_string()),
Node::String("instance".to_string()),
Node::String(".*".to_string()),
],
aggregation: None,
}))
);
}
#[test]
fn agg_functions() {
assert_eq!(
expression(cbs("sum(foo) by (bar) * count(foo) without (bar)"), false),
Ok((cbs(""), operator(
Function {
name: "sum".to_string(),
args: vec![vector("foo")],
aggregation: Some(AggregationMod {
action: AggregationAction::By,
labels: vec!["bar".to_string()]
}),
},
Mul(None),
Function {
name: "count".to_string(),
args: vec![vector("foo")],
aggregation: Some(AggregationMod {
action: AggregationAction::Without,
labels: vec!["bar".to_string()]
}),
},
)))
);
assert_eq!(
expression(cbs("sum by (bar) (foo) * count without (bar) (foo)"), false),
Ok((cbs(""), operator(
Function {
name: "sum".to_string(),
args: vec![vector("foo")],
aggregation: Some(AggregationMod {
action: AggregationAction::By,
labels: vec!["bar".to_string()]
}),
},
Mul(None),
Function {
name: "count".to_string(),
args: vec![vector("foo")],
aggregation: Some(AggregationMod {
action: AggregationAction::Without,
labels: vec!["bar".to_string()]
}),
},
)))
);
}
}