use std::panic::{RefUnwindSafe, UnwindSafe};
use std::path::PathBuf;
use std::sync::OnceLock;
use super::generators::draw_and_print_value;
use super::strings::CharactersGenerator;
use super::{Generator, PrintableGenerator, TestCase, combine_labels, label_from_name};
use crate::ffi;
use crate::pretty::PrettyPrinter;
use crate::test_case::invalid_argument;
const LARK_LABEL: u64 = label_from_name("hegel.from_lark");
const ATTEMPT_LABEL: u64 = label_from_name("hegel.from_lark.attempt");
const DEFAULT_MAX_SIZE: usize = 8192;
const ATTEMPTS: usize = 3;
type ExplicitTerminal = dyn Generator<String> + Send + Sync + UnwindSafe + RefUnwindSafe;
struct Explicit {
name: String,
generator: Box<ExplicitTerminal>,
}
struct Compiled {
grammar: ffi::Grammar,
markers: Vec<String>,
}
struct Terminal<'a>(&'a ExplicitTerminal);
impl Generator<String> for Terminal<'_> {
fn label(&self) -> u64 {
self.0.label()
}
fn do_draw(&self, tc: &TestCase) -> String {
self.0.do_draw(tc)
}
}
pub struct LarkGenerator {
text: String,
start: Option<String>,
explicit: Vec<Explicit>,
alphabet: Option<CharactersGenerator>,
max_size: Option<usize>,
max_depth: Option<u32>,
import_paths: Vec<PathBuf>,
label: u64,
handle: OnceLock<Compiled>,
}
impl LarkGenerator {
pub fn start(mut self, start: &str) -> Self {
self.handle = OnceLock::new();
self.start = Some(start.to_string());
self.relabel();
self
}
pub fn explicit<G>(mut self, name: &str, generator: G) -> Self
where
G: Generator<String> + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
{
self.handle = OnceLock::new();
self.explicit.push(Explicit {
name: name.to_string(),
generator: Box::new(generator),
});
self.relabel();
self
}
fn relabel(&mut self) {
let mut labels = vec![
LARK_LABEL,
label_from_name(&self.text),
label_from_name(self.start.as_deref().unwrap_or("start")),
];
for explicit in &self.explicit {
labels.push(label_from_name(&explicit.name));
labels.push(explicit.generator.label());
}
self.label = combine_labels(&labels);
}
pub fn alphabet(mut self, alphabet: CharactersGenerator) -> Self {
self.handle = OnceLock::new();
self.alphabet = Some(alphabet);
self
}
pub fn max_size(mut self, max_size: usize) -> Self {
self.handle = OnceLock::new();
self.max_size = Some(max_size);
self
}
pub fn max_depth(mut self, max_depth: u32) -> Self {
self.handle = OnceLock::new();
self.max_depth = Some(max_depth);
self
}
pub fn import_path(mut self, dir: impl Into<PathBuf>) -> Self {
self.handle = OnceLock::new();
self.import_paths.push(dir.into());
self
}
fn splice(
&self,
tc: &TestCase,
compiled: &Compiled,
budget: usize,
) -> (String, usize, usize, usize) {
let drawn = tc.generate_grammar(&compiled.grammar, budget as u64);
let mut out = String::with_capacity(drawn.len());
let mut own = 0;
let mut markers = 0;
let mut size = 0;
let mut rest = drawn.as_str();
let mut from = 0;
while let Some(at) = rest[from..]
.find(|c| compiled.markers.iter().any(|m| m.starts_with(c)))
.map(|at| from + at)
{
let Some(i) = compiled
.markers
.iter()
.position(|marker| rest[at..].starts_with(marker.as_str()))
else {
from = at + rest[at..].chars().next().unwrap().len_utf8();
continue;
};
own += rest[..at].chars().count();
out.push_str(&rest[..at]);
let value = tc.draw_silent(Terminal(&*self.explicit[i].generator));
size += value.chars().count();
markers += 1;
out.push_str(&value);
rest = &rest[at + compiled.markers[i].len()..];
from = 0;
}
own += rest.chars().count();
out.push_str(rest);
(out, own, markers, size + own)
}
fn handle(&self) -> &Compiled {
self.handle.get_or_init(|| {
if self.max_size == Some(0) {
invalid_argument!("max_size must be at least 1");
}
if self.max_depth == Some(0) {
invalid_argument!("max_depth must be at least 1");
}
let names: Vec<String> = self.explicit.iter().map(|e| e.name.clone()).collect();
if let Some(name) = names
.iter()
.find(|name| names.iter().filter(|n| n == name).count() > 1)
{
invalid_argument!("explicit terminal '{name}' is given more than one generator");
}
let alphabet = self.alphabet.as_ref().map(|a| a.build_alphabet_handle());
ffi::Grammar::new(
&self.text,
self.start.as_deref(),
self.max_size.unwrap_or(DEFAULT_MAX_SIZE) as u64,
self.max_depth.unwrap_or(0),
alphabet.as_ref(),
&names,
&self.import_paths,
)
.and_then(|grammar| {
let markers = names
.iter()
.map(|name| grammar.placeholder(name))
.collect::<Result<_, _>>()?;
Ok(Compiled { grammar, markers })
})
.unwrap_or_else(|msg| invalid_argument!("{msg}"))
})
}
}
impl Generator<String> for LarkGenerator {
fn label(&self) -> u64 {
self.label
}
fn do_draw(&self, tc: &TestCase) -> String {
let compiled = self.handle();
if compiled.markers.is_empty() {
return tc.generate_grammar(&compiled.grammar, 0);
}
let max_size = self.max_size.unwrap_or(DEFAULT_MAX_SIZE);
let mut budget = max_size;
for _ in 0..ATTEMPTS {
tc.start_span(ATTEMPT_LABEL);
let (out, own, markers, size) = self.splice(tc, compiled, budget);
if size <= max_size {
tc.stop_span(false);
return out;
}
tc.stop_span(true);
budget = ((max_size as u128 * (own + markers) as u128 / size as u128) as usize)
.min(budget.saturating_sub(1))
.max(1);
}
tc.reject()
}
}
impl PrintableGenerator<String> for LarkGenerator {
fn do_draw_and_print(&self, tc: &TestCase, printer: &mut PrettyPrinter) -> String {
draw_and_print_value(self, tc, printer)
}
}
pub fn from_lark(grammar: &str) -> LarkGenerator {
let mut generator = LarkGenerator {
text: grammar.to_string(),
start: None,
explicit: Vec::new(),
alphabet: None,
max_size: None,
max_depth: None,
import_paths: Vec::new(),
label: 0,
handle: OnceLock::new(),
};
generator.relabel();
generator
}