pub(crate) const REPETITION_SEGMENT: u64 = 64;
pub(crate) const MAX_SCHEMA_REPETITION_BOUND: u64 = 65_536;
const _: () = assert!(MAX_SCHEMA_REPETITION_BOUND / REPETITION_SEGMENT < 2000);
const _: () = assert!(REPETITION_SEGMENT < 2000);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Repetition {
pub expr: String,
pub rules: Vec<(String, String)>,
}
pub(crate) fn exact_repetition(atom: &str, min: u64, max: Option<u64>) -> Repetition {
debug_assert!(max.is_none_or(|upper| upper >= min));
let mut rules = Vec::new();
if max == Some(0) {
return Repetition {
expr: "\"\"".to_string(),
rules,
};
}
let symbol = if is_bare_symbol(atom) {
atom.to_string()
} else {
let name = format!("rep-atom-{:016x}", fnv1a(&[atom]));
rules.push((name.clone(), atom.to_string()));
name
};
let mut parts = exact_count(&symbol, min, &mut rules);
match max {
None => parts.push(format!("{symbol}*")),
Some(upper) if upper > min => parts.push(optional_up_to(&symbol, upper - min, &mut rules)),
Some(_) => {}
}
let body = parts.join(" ");
let name = format!("rep-{:016x}", fnv1a(&[&symbol, &body]));
push_rule(&mut rules, &name, body);
Repetition { expr: name, rules }
}
fn exact_count(symbol: &str, count: u64, rules: &mut Vec<(String, String)>) -> Vec<String> {
let mut parts = Vec::new();
match count / REPETITION_SEGMENT {
0 => {}
1 => parts.push(block_rule(symbol, rules)),
blocks => parts.push(format!("{}{{{blocks}}}", block_rule(symbol, rules))),
}
match count % REPETITION_SEGMENT {
0 => {}
1 => parts.push(symbol.to_string()),
rest => parts.push(format!("{symbol}{{{rest}}}")),
}
parts
}
fn block_rule(symbol: &str, rules: &mut Vec<(String, String)>) -> String {
let body = format!("{symbol}{{{REPETITION_SEGMENT}}}");
let name = format!("rep-block-{:016x}", fnv1a(&[&body]));
push_rule(rules, &name, body);
name
}
fn optional_up_to(symbol: &str, count: u64, rules: &mut Vec<(String, String)>) -> String {
debug_assert!(count > 0);
let levels = (count - 1) / REPETITION_SEGMENT;
let innermost = count - levels * REPETITION_SEGMENT;
let mut expression = if innermost == 1 {
format!("{symbol}?")
} else {
format!("{symbol}{{0,{innermost}}}")
};
if levels == 0 {
return expression;
}
let below_body = format!("{symbol}{{0,{}}}", REPETITION_SEGMENT - 1);
let below = format!("rep-below-{:016x}", fnv1a(&[&below_body]));
push_rule(rules, &below, below_body);
let block = block_rule(symbol, rules);
for _ in 0..levels {
let body = format!("{below} | {block} {expression}");
let name = format!("rep-opt-{:016x}", fnv1a(&[&body]));
push_rule(rules, &name, body);
expression = name;
}
expression
}
fn push_rule(rules: &mut Vec<(String, String)>, name: &str, body: String) {
if !rules.iter().any(|(existing, _)| existing == name) {
rules.push((name.to_string(), body));
}
}
fn is_bare_symbol(atom: &str) -> bool {
!atom.is_empty()
&& atom
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_')
}
fn fnv1a(parts: &[&str]) -> u64 {
let mut hash = 0xcbf29ce484222325u64;
for part in parts {
for byte in part.bytes().chain(std::iter::once(0)) {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x100000001b3);
}
}
hash
}
#[cfg(test)]
mod tests {
use super::super::parser::parse_generated;
use super::super::sampler::GrammarRuntime;
use super::*;
fn grammar(min: u64, max: Option<u64>) -> String {
let repetition = exact_repetition("x", min, max);
let mut source = format!("root ::= \"[\" {} \"]\"\nx ::= \"a\"\n", repetition.expr);
for (name, body) in repetition.rules {
source.push_str(&format!("{name} ::= {body}\n"));
}
source
}
fn accepts(source: &str, count: usize) -> (bool, usize) {
let parsed = parse_generated(source).unwrap_or_else(|error| panic!("{error}\n{source}"));
let root = parsed.rule_id("root").unwrap();
let mut runtime = GrammarRuntime::new(parsed, root).unwrap();
let mut peak = runtime.stacks.len();
let mut ok = runtime.accept_bytes(b"[");
for _ in 0..count {
if !ok {
break;
}
ok = runtime.accept_bytes(b"a");
peak = peak.max(runtime.stacks.len());
}
(
ok && runtime.accept_bytes(b"]") && runtime.is_accepted(),
peak,
)
}
#[test]
fn segments_stay_within_parser_threshold() {
for (min, max) in [
(0, Some(1999)),
(0, Some(2000)),
(0, Some(2001)),
(2001, Some(4096)),
(4096, None),
(0, Some(MAX_SCHEMA_REPETITION_BOUND)),
(
MAX_SCHEMA_REPETITION_BOUND,
Some(MAX_SCHEMA_REPETITION_BOUND),
),
] {
let source = grammar(min, max);
parse_generated(&source)
.unwrap_or_else(|error| panic!("{min}..{max:?}: {error}\n{source}"));
}
}
#[test]
fn composed_bounds_are_exact() {
for (min, max) in [
(0, Some(64)),
(0, Some(65)),
(63, Some(129)),
(128, Some(128)),
(0, Some(2000)),
(0, Some(2001)),
(1999, Some(2001)),
(2001, Some(2001)),
(0, Some(4096)),
(2500, Some(4096)),
] {
let source = grammar(min, max);
let upper = max.unwrap() as usize;
let lower = min as usize;
assert!(accepts(&source, upper).0, "{min}..{max:?} rejects {upper}");
assert!(accepts(&source, lower).0, "{min}..{max:?} rejects {lower}");
assert!(
!accepts(&source, upper + 1).0,
"{min}..{max:?} accepts {}",
upper + 1
);
if lower > 0 {
assert!(
!accepts(&source, lower - 1).0,
"{min}..{max:?} accepts {}",
lower - 1
);
}
}
let unbounded = grammar(2001, None);
assert!(!accepts(&unbounded, 2000).0);
assert!(accepts(&unbounded, 2001).0);
assert!(accepts(&unbounded, 5000).0);
}
#[test]
fn composition_keeps_runtime_stacks_bounded() {
let source = grammar(0, Some(MAX_SCHEMA_REPETITION_BOUND));
let full = MAX_SCHEMA_REPETITION_BOUND as usize;
let (accepted, peak) = accepts(&source, full);
assert!(accepted);
assert!(peak <= 8, "unambiguous composition peaked at {peak} stacks");
assert!(!accepts(&source, full + 1).0);
}
}