use nom::{
AsBytes,
AsChar,
Compare,
FindToken,
InputIter,
InputLength,
InputTake,
InputTakeAtPosition,
Offset,
ParseTo,
Slice,
};
use std::ops::{
Range,
RangeFrom,
RangeTo,
};
use nom::branch::alt;
use nom::bytes::complete::{
tag,
tag_no_case,
};
use nom::character::complete::char;
use nom::combinator::{
map,
opt,
};
use nom::multi::{
many0,
separated_list0,
};
use nom::number::complete::float;
use nom::sequence::{
delimited,
preceded,
tuple,
};
use vec::{label_name, vector, Vector};
use crate::{
ParserOptions,
tuple_separated,
};
use crate::str::string;
use crate::whitespace::{
ws_or_comment,
surrounded_ws_or_comment,
};
use crate::utils::{
IResult,
delimited_ws,
value,
};
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
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, Clone)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub enum OpModAction {
RestrictTo,
Ignore,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub struct OpMod {
pub action: OpModAction,
pub labels: Vec<String>,
pub group: Option<OpGroupMod>,
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub enum OpGroupSide {
Left,
Right,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub struct OpGroupMod {
pub side: OpGroupSide,
pub labels: Vec<String>,
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub enum AggregationAction {
Without,
By,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub struct AggregationMod {
pub action: AggregationAction,
pub labels: Vec<String>,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serializable", derive(serde_derive::Serialize))]
pub enum Node {
Operator {
op: Op,
args: Vec<Node>,
},
Vector(Vector),
Scalar(f32),
String(Vec<u8>),
Function {
name: String,
args: Vec<Node>,
aggregation: Option<AggregationMod>,
},
Negation(Box<Node>),
}
impl Node {
fn negation(x: Node) -> Node {
Node::Negation(Box::new(x))
}
}
fn label_list<I, C>(input: I, opts: &ParserOptions) -> IResult<I, Vec<String>>
where
I: Clone
+ AsBytes
+ Compare<&'static str>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
,
C: AsChar + Clone,
&'static str: FindToken<C>,
{
delimited_ws(
char('('),
separated_list0(surrounded_ws_or_comment(opts, char(',')), label_name),
char(')')
)(input)
}
fn function_aggregation<I, C>(input: I, opts: &ParserOptions) -> IResult<I, AggregationMod>
where
I: Clone
+ AsBytes
+ Compare<&'static str>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
,
C: AsChar + Clone,
&'static str: FindToken<C>,
{
surrounded_ws_or_comment(opts, map(
tuple_separated!(ws_or_comment(opts), (
alt((
value(tag("by"), AggregationAction::By),
value(tag("without"), AggregationAction::Without),
)),
|i| label_list(i, opts),
)),
|(action, labels)| (AggregationMod { action, labels })
))(input)
}
fn function_args<'a, I, C>(recursion_level: usize, opts: &'a ParserOptions) -> impl FnMut(I) -> IResult<I, Vec<Node>> + 'a
where
I: Clone + Copy
+ AsBytes
+ Compare<&'static str>
+ for<'b> Compare<&'b [u8]>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ ParseTo<f32>
+ 'a
,
C: AsChar + Clone + Copy + 'a,
&'static str: FindToken<C>,
<I as InputIter>::IterElem: Clone,
{
delimited_ws(
char('('),
separated_list0(
surrounded_ws_or_comment(opts, char(',')),
alt((
move |i| expression(recursion_level, i, opts),
map(string, Node::String),
))
),
char(')')
)
}
macro_rules! pair_permutations {
($p1:expr, $p2:expr $(,)?) => {
alt((
tuple(($p1, $p2)),
map(
tuple(($p2, $p1)),
|(o2, o1)| (o1, o2),
),
))
};
}
fn function<I, C>(recursion_level: usize, input: I, opts: &ParserOptions) -> IResult<I, Node>
where
I: Clone + Copy
+ AsBytes
+ Compare<&'static str>
+ for<'a> Compare<&'a [u8]>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ ParseTo<f32>
,
C: AsChar + Clone + Copy,
&'static str: FindToken<C>,
<I as InputIter>::IterElem: Clone,
{
map(
tuple((
label_name,
pair_permutations!(
function_args(recursion_level, opts),
opt(|i| function_aggregation(i, opts)),
),
)),
|(name, (args, agg))|
Node::Function {
name,
args,
aggregation: agg,
}
)(input)
}
fn atom<I, C>(recursion_level: usize, input: I, opts: &ParserOptions) -> IResult<I, Node>
where
I: Clone + Copy
+ AsBytes
+ Compare<&'static str>
+ for<'a> Compare<&'a [u8]>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ ParseTo<f32>
,
C: AsChar + Clone + Copy,
&'static str: FindToken<C>,
<I as InputIter>::IterElem: Clone,
{
if recursion_level > opts.recursion_limit {
return Err(
nom::Err::Failure(
nom::error::VerboseError {
errors: vec![
(input, nom::error::VerboseErrorKind::Context("reached recursion limit")),
]
}
)
);
}
surrounded_ws_or_comment(opts,
alt((
alt((
map(
tag_no_case("NaN"),
|_| Node::Scalar(::std::f32::NAN)
), map(
float,
Node::Scalar
),
)),
preceded(
char('+'),
|i| atom(recursion_level+1, i, opts)
),
map(
preceded(
char('-'),
|i| atom(recursion_level+1, i, opts)
),
Node::negation
),
alt((
|i| function(recursion_level, i, opts),
map(
|i| vector(i, opts),
Node::Vector
),
delimited(
char('('),
|i| expression(recursion_level, i, opts),
char(')')
)
))
))
)(input)
}
fn with_modifier<'a, I, C>(opts: &'a ParserOptions, literal: &'static str, op: fn(Option<OpMod>) -> Op) -> impl FnMut(I) -> IResult<I, Op> + 'a
where
I: Clone
+ AsBytes
+ Compare<&'static str>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ 'a
,
C: AsChar + Clone,
&'static str: FindToken<C>,
{
map(
preceded(
tag(literal),
opt(move |i| op_modifier(i, opts)),
),
op,
)
}
fn with_bool_modifier<'a, I, C, O: Fn(bool, Option<OpMod>) -> Op + 'a>(opts: &'a ParserOptions, literal: &'static str, op: O) -> impl FnMut(I) -> IResult<I, Op> + 'a
where
I: Clone
+ AsBytes
+ Compare<&'static str>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ 'a
,
C: AsChar + Clone,
&'static str: FindToken<C>,
{
map(
tuple_separated!(ws_or_comment(opts), (
tag(literal),
opt(tag("bool")),
opt(move |i| op_modifier(i, opts)),
)),
move |(_, boolness, op_mod)|
op(boolness.is_some(), op_mod)
)
}
fn op_modifier<I, C>(input: I, opts: &ParserOptions) -> IResult<I, OpMod>
where
I: Clone
+ AsBytes
+ Compare<&'static str>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
,
C: AsChar + Clone,
&'static str: FindToken<C>,
{
surrounded_ws_or_comment(opts, map(
tuple_separated!(ws_or_comment(opts), (
alt((
value(tag("on"), OpModAction::RestrictTo),
value(tag("ignoring"), OpModAction::Ignore),
)),
|i| label_list(i, opts),
opt(map(
tuple_separated!(ws_or_comment(opts), (
alt((
value(tag("group_left"), OpGroupSide::Left),
value(tag("group_right"), OpGroupSide::Right),
)),
map(
opt(|i| label_list(i, opts)),
|labels| labels.unwrap_or_default()
),
)),
|(side, labels)|
(OpGroupMod { side, labels })
)),
)),
|(action, labels, group)|
(OpMod { action, labels, group })
))(input)
}
macro_rules! op_matcher {
($($type:path),+) => (
|op| match op {
$($type(..) => true,)+
_ => false,
}
)
}
fn collapse_ops(args: &mut Vec<Node>, ops: &mut Vec<Op>, rtl: bool, op_matcher: fn(&Op) -> bool) {
loop {
let mut i = ops.iter();
let i = if rtl {
i.rposition(op_matcher)
} else {
i.position(op_matcher)
};
let i = match i {
Some(i) => i,
None => break,
};
let arg1 = args.remove(i);
let arg2 = args.remove(i);
let op = ops.remove(i);
let node = match arg1 {
Node::Operator { op: lhs_op, mut args } if op == lhs_op => {
args.push(arg2);
Node::Operator {
op: lhs_op,
args,
}
},
_ => Node::Operator {
op,
args: vec![arg1, arg2],
}
};
args.insert(i, node);
}
}
fn parse_ops<I, C>(recursion_level: usize, input: I, opts: &ParserOptions) -> IResult<I, Node>
where
I: Clone + Copy
+ AsBytes
+ Compare<&'static str>
+ for<'a> Compare<&'a [u8]>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ ParseTo<f32>
,
C: AsChar + Clone + Copy,
&'static str: FindToken<C>,
<I as InputIter>::IterElem: Clone
{
let (tail, ops_args) = many0(tuple((
|i| atom(recursion_level, i, opts),
alt((
with_modifier(opts, "or", Op::Or),
with_bool_modifier(opts, "==", Op::Eq),
with_bool_modifier(opts, "!=", Op::Ne),
with_bool_modifier(opts, "<=", Op::Le),
with_bool_modifier(opts, ">=", Op::Ge),
with_bool_modifier(opts, "<", Op::Lt),
with_bool_modifier(opts, ">", Op::Gt),
with_modifier(opts, "and", Op::And),
with_modifier(opts, "unless", Op::Unless),
with_modifier(opts, "+", Op::Plus),
with_modifier(opts, "-", Op::Minus),
with_modifier(opts, "*", Op::Mul),
with_modifier(opts, "/", Op::Div),
with_modifier(opts, "%", Op::Mod),
with_modifier(opts, "^", Op::Pow),
))
)))(input)?;
let (tail, last_arg) = atom(recursion_level, tail, opts)?;
if ops_args.is_empty() {
return Ok((tail, last_arg))
}
let (mut args, mut ops): (Vec<_>, Vec<_>) = ops_args.into_iter().unzip();
args.push(last_arg);
collapse_ops(&mut args, &mut ops, true, op_matcher!(Op::Pow));
collapse_ops(&mut args, &mut ops, false, op_matcher!(Op::Mul, Op::Div, Op::Mod));
collapse_ops(&mut args, &mut ops, false, op_matcher!(Op::Plus, Op::Minus));
collapse_ops(&mut args, &mut ops, false, op_matcher!(Op::Eq, Op::Ne, Op::Le, Op::Ge, Op::Lt, Op::Gt));
collapse_ops(&mut args, &mut ops, false, op_matcher!(Op::And, Op::Unless));
collapse_ops(&mut args, &mut ops, false, op_matcher!(Op::Or));
assert!(ops.is_empty(), "Leftover ops: {:?}", ops);
assert_eq!(args.len(), 1, "Leftover args: {:?}", args);
Ok((
tail,
args.pop().unwrap(),
))
}
pub(crate) fn expression<I, C>(recursion_level: usize, input: I, opts: &ParserOptions) -> IResult<I, Node>
where
I: Clone + Copy
+ AsBytes
+ Compare<&'static str>
+ for<'a> Compare<&'a [u8]>
+ InputIter<Item = C>
+ InputLength
+ InputTake
+ InputTakeAtPosition<Item = C>
+ Offset
+ Slice<Range<usize>>
+ Slice<RangeFrom<usize>>
+ Slice<RangeTo<usize>>
+ ParseTo<f32>
,
C: AsChar + Clone + Copy,
&'static str: FindToken<C>,
<I as InputIter>::IterElem: Clone,
{
if recursion_level > opts.recursion_limit {
return Err(
nom::Err::Failure(
nom::error::VerboseError {
errors: vec![
(input, nom::error::VerboseErrorKind::Context("reached recursion limit")),
]
}
)
);
}
parse_ops(recursion_level+1, input, opts)
}
#[allow(unused_imports)]
#[cfg(test)]
mod tests {
use super::*;
use crate::vec;
use self::Node::{Function, Scalar};
use self::Op::*;
use nom::error::{
VerboseError,
VerboseErrorKind,
};
fn operator(x: Node, op: Op, y: Node) -> Node {
Node::Operator {
op,
args: vec![x, y],
}
}
#[allow(non_upper_case_globals)]
const negation: fn(Node) -> Node = Node::negation;
fn vector(expr: &str) -> Node {
match vec::vector(expr, &ParserOptions::default()) {
Ok(("", x)) => Node::Vector(x),
_ => panic!("failed to parse label correctly"),
}
}
#[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(0, input, &Default::default()), Ok(("", Scalar(output))));
}
#[test]
fn ops() {
assert_eq!(
expression(0,
"foo > bar != 0 and 15.5 < xyzzy",
&Default::default(),
),
Ok((
"",
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(0,
"foo + bar - baz <= quux + xyzzy",
&Default::default(),
),
Ok((
"",
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(0,
"foo + bar % baz",
&Default::default(),
),
Ok((
"",
operator(
vector("foo"),
Plus(None),
operator(vector("bar"), Mod(None), vector("baz")),
)
))
);
assert_eq!(
expression(0,
"x^y^z",
&Default::default(),
),
Ok((
"",
operator(
vector("x"),
Pow(None),
operator(vector("y"), Pow(None), vector("z")),
)
))
);
assert_eq!(
expression(0,
"(a+b)*c",
&Default::default(),
),
Ok((
"",
operator(
operator(vector("a"), Plus(None), vector("b")),
Mul(None),
vector("c"),
)
))
);
}
#[test]
fn op_mods() {
assert_eq!(
expression(0,
"foo + ignoring (instance) bar / on (cluster) baz",
&Default::default(),
),
Ok((
"",
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(0,
"foo + ignoring (instance) group_right bar / on (cluster, shmuster) group_left (job) baz",
&Default::default(),
),
Ok(("", 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(0,
"node_cpu{cpu='cpu0'} > bool ignoring (cpu) node_cpu{cpu='cpu1'}",
&Default::default(),
),
Ok((
"",
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(0,
"a + -b",
&Default::default(),
),
Ok((
"",
operator(vector("a"), Plus(None), negation(vector("b")),)
))
);
assert_eq!(
expression(0,
"a ^ - 1 - b",
&Default::default(),
),
Ok((
"",
operator(
operator(vector("a"), Pow(None), negation(Scalar(1.)),),
Minus(None),
vector("b"),
)
))
);
assert_eq!(
expression(0,
"a ^ - (1 - b)",
&Default::default(),
),
Ok((
"",
operator(
vector("a"),
Pow(None),
negation(operator(Scalar(1.), Minus(None), vector("b"),)),
)
))
);
assert_eq!(
expression(0,
"a +++++++ b",
&Default::default(),
),
Ok(("", operator(vector("a"), Plus(None), vector("b"),)))
);
assert_eq!(
expression(0,
"a * --+-b",
&Default::default(),
),
Ok((
"",
operator(
vector("a"),
Mul(None),
negation(negation(negation(vector("b")))),
)
))
);
}
#[test]
fn functions() {
assert_eq!(
expression(0,
"foo() + bar(baz) + quux(xyzzy, plough)",
&Default::default(),
),
Ok((
"",
Node::Operator {
op: Plus(None),
args: vec![
Function {
name: "foo".to_string(),
args: vec![],
aggregation: None,
},
Function {
name: "bar".to_string(),
args: vec![vector("baz")],
aggregation: None,
},
Function {
name: "quux".to_string(),
args: vec![vector("xyzzy"), vector("plough"),],
aggregation: None,
},
],
}
))
);
assert_eq!(
expression(0,
"round(rate(whatever [5m]) > 0, 0.2)",
&Default::default(),
),
Ok((
"",
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(0,
"label_replace(up, 'instance', '', 'instance', '.*')",
&Default::default(),
),
Ok((
"",
Function {
name: "label_replace".to_string(),
args: vec![
vector("up"),
Node::String(b"instance".to_vec()),
Node::String(b"".to_vec()),
Node::String(b"instance".to_vec()),
Node::String(b".*".to_vec()),
],
aggregation: None,
}
))
);
}
#[test]
fn agg_functions() {
assert_eq!(
expression(0,
"sum(foo) by (bar) * count(foo) without (bar)",
&Default::default(),
),
Ok((
"",
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(0,
"sum by (bar) (foo) * count without (bar) (foo)",
&Default::default(),
),
Ok((
"",
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()]
}),
},
)
))
);
}
#[test]
fn comments() {
let opts = ParserOptions::new()
.comments(true)
.build();
assert_eq!(
expression(0, "foo # / bar\n/ baz", &opts),
Ok((
"",
operator(vector("foo"), Div(None), vector("baz"))
))
);
assert_eq!(
expression(0, "sum(foo) # by (bar)\nby (baz)", &opts),
Ok((
"",
Function {
name: "sum".to_string(),
args: vec![vector("foo")],
aggregation: Some(AggregationMod {
action: AggregationAction::By,
labels: vec!["baz".to_string()]
}),
},
))
);
}
#[test]
fn recursion_limit() {
let opts = super::ParserOptions::new()
.recursion_limit(8)
.build();
let mut op = String::new();
for _ in 1..=9 {
op.push('+');
}
assert_eq!(
expression(0, format!("a {} b", op).as_str(), &opts),
Err(nom::Err::Failure(VerboseError {
errors: vec![
(&" b"[..], VerboseErrorKind::Context("reached recursion limit")),
],
})),
);
op.push('+');
assert_eq!(
expression(0, format!("a {} b", op).as_str(), &opts),
Err(nom::Err::Failure(VerboseError {
errors: vec![
(&"+ b"[..], VerboseErrorKind::Context("reached recursion limit")),
],
})),
);
}
#[test]
fn stack_overflow() {
let opts = super::ParserOptions::new()
.recursion_limit(1024)
.build();
let mut op = String::new();
for _ in 1..256 {
op.push('+');
dbg!(op.len());
use std::io::Write;
std::io::stdout().flush().unwrap();
let _ = expression(0, format!("a {} b", op).as_str(), &opts);
}
}
}