use cel::parser::Parser;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
struct BenchTestInfo {
input: String,
expect_err: bool,
}
struct BenchCategory {
name: &'static str,
cases: Vec<BenchTestInfo>,
}
fn bench_categories() -> Vec<BenchCategory> {
vec![
BenchCategory {
name: "Simple",
cases: vec![
BenchTestInfo {
input: "x * 2 + y / 3".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "foo.bar.baz(1, 2, \"abc\")".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "a > 5 && b < 10 || c == \"xyz\"".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "x ? y : z".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "{\"foo\": 1, \"bar\": [2, 3]}".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "a[b]".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "a.b.c".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "a.`b-c`".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "\"\\a\\b\\f\\n\\r\\t\\v'\\\"\\\\ Legal escapes \\u2764\"".to_string(),
expect_err: false,
},
],
},
BenchCategory {
name: "Complex",
cases: vec![
BenchTestInfo {
input: "a".to_string() + &" + a".repeat(49),
expect_err: false,
},
BenchTestInfo {
input: "a".to_string() + &" || a".repeat(49),
expect_err: false,
},
BenchTestInfo {
input: "a".to_string() + &".f".repeat(49),
expect_err: false,
},
BenchTestInfo {
input: "(".repeat(20) + "a" + &")".repeat(20),
expect_err: false,
},
BenchTestInfo {
input: "SomeMessage{foo: 5, bar: \"xyz\"}".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "1 + 2 * 3 - 1 / 2 == 6 % 1".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "[] + [1, 2, 3] + [4]".to_string(),
expect_err: false,
},
],
},
BenchCategory {
name: "Macros",
cases: vec![
BenchTestInfo {
input: "has(m.f)".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "[1, 2, 3].all(x, x > 0)".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "m.map(v, v * 2)".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "m.filter(v, v > 0)".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "m.exists_one(v, v == 1)".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "x.filter(y, y.exists(z, has(z.a)))".to_string(),
expect_err: false,
},
BenchTestInfo {
input: "a.?b[?0] && a[?c]".to_string(),
expect_err: false,
},
],
},
BenchCategory {
name: "Errors",
cases: vec![
BenchTestInfo {
input: "x * 2 + y /".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "foo.bar.baz(1, 2, \"abc\"".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "a > 5 && && b < 10".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "{\"foo\": 1, \"bar\": [2, 3".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "1 + $".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "break".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "\"\\xFh\"".to_string(),
expect_err: true,
},
BenchTestInfo {
input: "a".to_string() + &" + a".repeat(49) + " +",
expect_err: true,
},
BenchTestInfo {
input: "(".repeat(20) + "a",
expect_err: true,
},
BenchTestInfo {
input: "f(*".to_string() + &", *".repeat(9) + ")",
expect_err: true,
},
],
},
]
}
fn new_antlr_parser() -> Parser {
Parser::default()
.enable_optional_syntax(true)
.max_recursion_depth(512)
}
#[cfg(feature = "parser_pratt")]
fn new_pratt_parser() -> cel::parser::PrattParser {
cel::parser::PrattParser::default()
.enable_optional_syntax(true)
.enable_ident_escape_syntax(true)
.max_recursion_depth(512)
}
#[derive(Debug)]
#[non_exhaustive]
enum BackendParser {
#[allow(clippy::upper_case_acronyms)]
ANTLR,
Pratt,
}
pub fn benchmark_by_category(c: &mut Criterion) {
let categories = bench_categories();
let backends = if cfg!(feature = "parser_pratt") {
vec![BackendParser::ANTLR, BackendParser::Pratt]
} else {
vec![BackendParser::ANTLR]
};
for backend in backends {
let mut group = c.benchmark_group(format!("by_category/{backend:?}"));
for cat in &categories {
group.bench_function(BenchmarkId::from_parameter(cat.name), |b| {
b.iter(|| {
for tc in &cat.cases {
let is_err = match backend {
BackendParser::ANTLR => {
let parser = new_antlr_parser();
parser.parse(black_box(&tc.input)).is_err()
}
#[cfg(feature = "parser_pratt")]
BackendParser::Pratt => {
let parser = new_pratt_parser();
parser.parse(black_box(&tc.input)).is_err()
}
#[allow(unreachable_patterns)]
_ => panic!("Unsupported parser {backend:?}"),
};
assert_eq!(is_err, tc.expect_err, "Failed test case: {}", tc.input);
}
});
});
}
group.finish();
}
}
pub fn benchmark_by_category_comparison(c: &mut Criterion) {
let categories = bench_categories();
for cat in &categories {
let mut group = c.benchmark_group(cat.name);
group.bench_function("antlr", |b| {
b.iter(|| {
for tc in &cat.cases {
let res = new_antlr_parser().parse(black_box(&tc.input));
assert_eq!(res.is_err(), tc.expect_err, "Failed: {}", tc.input);
}
});
});
#[cfg(feature = "parser_pratt")]
group.bench_function("pratt", |b| {
b.iter(|| {
for tc in &cat.cases {
let res = new_pratt_parser().parse(black_box(&tc.input));
assert_eq!(res.is_err(), tc.expect_err, "Failed: {}", tc.input);
}
});
});
group.finish();
}
}
criterion_group!(
benches,
benchmark_by_category,
benchmark_by_category_comparison
);
criterion_main!(benches);