use super::FollowCache;
use super::k_tuples::KTuplesBuilder;
use crate::analysis::FirstCache;
use crate::analysis::compiled_terminal::CompiledTerminal;
use crate::grammar::cfg::{NonTerminalIndexFn, TerminalIndexFn};
use crate::grammar::symbol_string::SymbolString;
use crate::{GrammarConfig, KTuples, Pos, Pr, Symbol};
use parol_runtime::TerminalIndex;
use parol_runtime::lexer::FIRST_USER_TOKEN;
use parol_runtime::log::trace;
use rustc_hash::FxHashMap;
use std::cell::RefCell;
use std::rc::Rc;
#[cfg(feature = "profiling")]
macro_rules! profile_scope {
($name:expr) => {
#[cfg(feature = "profiling")]
let _profile = profiling::ProfileScope::new($name);
};
}
type DomainType = KTuples;
type DomainTypeBuilder<'a> = KTuplesBuilder<'a>;
#[derive(Debug, Clone, Default)]
pub struct FollowSet {
pub non_terminals: Vec<DomainType>,
}
impl FollowSet {
pub fn new(non_terminals: Vec<DomainType>) -> Self {
FollowSet { non_terminals }
}
pub fn is_empty(&self) -> bool {
self.non_terminals.is_empty()
}
}
pub(crate) type ResultMap = FxHashMap<Pos, DomainType>;
#[derive(Clone)]
enum FollowPart {
TerminalSet(DomainType),
FirstOfNonTerminal(usize),
}
#[derive(Clone)]
struct FollowEquation {
pos: Pos,
target_nt_index: usize,
source_nt_index: usize,
rhs_parts: Vec<FollowPart>,
}
type EquationSystem = Vec<FollowEquation>;
type StepFunction = Box<dyn Fn(Rc<ResultMap>, Rc<RefCell<FollowSet>>) -> ResultMap>;
#[inline(always)]
pub fn follow_k(
grammar_config: &GrammarConfig,
k: usize,
first_cache: &FirstCache,
follow_cache: &FollowCache,
) -> (ResultMap, FollowSet) {
#[cfg(feature = "profiling")]
profile_scope!("follow_k_total");
let cfg = &grammar_config.cfg;
let terminals = grammar_config.cfg.get_ordered_terminals_owned();
let max_terminal_index = terminals.len() + FIRST_USER_TOKEN as usize;
let ti = Rc::new(grammar_config.cfg.get_terminal_index_function());
let first_k_of_nt = first_cache.get(k, grammar_config);
let start_symbol = cfg.get_start_symbol();
let nti = Rc::new(cfg.get_non_terminal_index_function());
let equation_system: Rc<EquationSystem> = Rc::new({
#[cfg(feature = "profiling")]
profile_scope!("equation_system_build");
cfg.pr.iter().enumerate().fold(Vec::new(), |es, (i, pr)| {
let args = UpdateProductionEquationsArgs {
prod_num: i,
pr,
ti: Rc::clone(&ti),
nti: Rc::clone(&nti),
k,
max_terminal_index,
};
update_production_equations(es, args)
})
});
trace!(
"FOLLOW({}): {} equations in equation system",
k,
equation_system.len()
);
let step_function: StepFunction = {
let equation_system = Rc::clone(&equation_system);
let first_k_of_nt = Rc::clone(&first_k_of_nt);
let epsilon_set = DomainTypeBuilder::new()
.k(k)
.max_terminal_index(max_terminal_index)
.eps()
.unwrap();
Box::new(
move |result_map: Rc<ResultMap>, non_terminal_results: Rc<RefCell<FollowSet>>| {
let mut new_result_vector = ResultMap::with_capacity_and_hasher(
result_map.len(),
rustc_hash::FxBuildHasher,
);
for equation in equation_system.iter() {
let mut pos_result = epsilon_set.clone();
{
let borrowed_first = first_k_of_nt.borrow();
for part in &equation.rhs_parts {
pos_result = match part {
FollowPart::TerminalSet(terminal_set) => {
pos_result.k_concat(terminal_set, k)
}
FollowPart::FirstOfNonTerminal(nt_index) => {
debug_assert!(*nt_index < borrowed_first.non_terminals.len());
let first_of_nt = &borrowed_first.non_terminals[*nt_index];
pos_result.k_concat(first_of_nt, k)
}
};
}
}
{
let borrowed_nt_results = non_terminal_results.borrow();
debug_assert!(
equation.source_nt_index < borrowed_nt_results.non_terminals.len()
);
let nt_follow_set =
&borrowed_nt_results.non_terminals[equation.source_nt_index];
pos_result = pos_result.k_concat(nt_follow_set, k);
}
{
let mut borrowed = non_terminal_results.borrow_mut();
debug_assert!(equation.target_nt_index < borrowed.non_terminals.len());
let set = &mut borrowed.non_terminals[equation.target_nt_index];
let _changed = set.union_in_place(&pos_result);
}
new_result_vector.insert(equation.pos, pos_result);
}
new_result_vector
},
)
};
let non_terminal_results = Rc::new(RefCell::new(FollowSet::new(
cfg.get_non_terminal_set()
.iter()
.fold(Vec::new(), |mut acc, nt| {
if nt == start_symbol {
acc.push(
DomainTypeBuilder::new()
.k(k)
.max_terminal_index(max_terminal_index)
.end()
.unwrap(),
);
} else {
acc.push(
DomainTypeBuilder::new()
.k(k)
.max_terminal_index(max_terminal_index)
.build()
.unwrap(),
);
}
acc
}),
)));
let mut result_map = if k == 0 {
let mut initial_map =
ResultMap::with_capacity_and_hasher(equation_system.len(), rustc_hash::FxBuildHasher);
for equation in equation_system.iter() {
initial_map.insert(
equation.pos,
DomainTypeBuilder::new()
.k(k)
.max_terminal_index(max_terminal_index)
.build()
.unwrap(),
);
}
Rc::new(initial_map)
} else {
let cache_ref = follow_cache.get(k - 1, grammar_config, first_cache);
let borrowed_cache = cache_ref.borrow();
let mut cached = ResultMap::with_capacity_and_hasher(
borrowed_cache.last_result.len(),
rustc_hash::FxBuildHasher,
);
for (p, t) in borrowed_cache.last_result.iter() {
cached.insert(*p, t.clone().set_k(k));
}
drop(borrowed_cache); Rc::new(cached)
};
let mut iterations = 0usize;
let mut new_result_vector;
loop {
#[cfg(feature = "profiling")]
profile_scope!("iteration_step");
new_result_vector = step_function(Rc::clone(&result_map), Rc::clone(&non_terminal_results));
if new_result_vector == *result_map {
break;
}
result_map = Rc::new(new_result_vector);
iterations += 1;
trace!("Iteration number {iterations} completed");
}
#[cfg(feature = "profiling")]
profiling::output_profiling_data();
(
new_result_vector,
Rc::try_unwrap(non_terminal_results).unwrap().into_inner(),
)
}
struct UpdateProductionEquationsArgs<'a, T, N> {
prod_num: usize,
pr: &'a Pr,
ti: Rc<T>,
nti: Rc<N>,
k: usize,
max_terminal_index: usize,
}
fn update_production_equations<T, N>(
mut es: EquationSystem,
args: UpdateProductionEquationsArgs<T, N>,
) -> EquationSystem
where
T: TerminalIndexFn,
N: NonTerminalIndexFn,
{
let pr_symbols = args.pr.get_r();
let mut parts = Vec::<(usize, SymbolString)>::with_capacity(pr_symbols.len());
for (i, s) in pr_symbols.iter().enumerate() {
match s {
Symbol::N(..) => parts.push((i + 1, SymbolString(vec![s.clone()]))),
Symbol::T(_) => {
if parts.is_empty() {
parts.push((i + 1, SymbolString(vec![s.clone()])));
} else if let Some((_, last_symbol_string)) = parts.last_mut() {
if matches!(last_symbol_string.0.last(), Some(Symbol::T(_))) {
last_symbol_string.0.push(s.clone());
} else {
parts.push((i + 1, SymbolString(vec![s.clone()])));
}
}
}
_ => {
unreachable!(
"Scanner switching directives have been removed from the grammar syntax."
);
}
}
}
for (part_index, (symbol_index, symbol_string)) in parts.iter().enumerate() {
if let Symbol::N(nt_at_position, _, _, _) = &symbol_string.0[0] {
let mut rhs_parts = Vec::with_capacity(parts.len().saturating_sub(part_index + 1));
for (_, symbol_string) in parts.iter().skip(part_index + 1) {
let symbol = &symbol_string.0[0]; match symbol {
Symbol::T(_) => {
let terminal_indices: Vec<TerminalIndex> = symbol_string
.0
.iter()
.map(|s| CompiledTerminal::create(s, Rc::clone(&args.ti)).0)
.collect();
let domain_type = DomainTypeBuilder::new()
.k(args.k)
.max_terminal_index(args.max_terminal_index)
.terminal_indices(&[&terminal_indices])
.build()
.unwrap();
rhs_parts.push(FollowPart::TerminalSet(domain_type));
}
Symbol::N(nt, _, _, _) => {
rhs_parts.push(FollowPart::FirstOfNonTerminal(
args.nti.non_terminal_index(nt),
));
}
_ => {
unreachable!(
"Scanner switching directives have been removed from the grammar syntax."
);
}
}
}
es.push(FollowEquation {
pos: (args.prod_num, *symbol_index).into(),
target_nt_index: args.nti.non_terminal_index(nt_at_position),
source_nt_index: args.nti.non_terminal_index(args.pr.get_n_str()),
rhs_parts,
});
}
}
es
}
#[cfg(feature = "profiling")]
mod profiling {
use rustc_hash::FxHashMap;
use std::cell::RefCell;
use std::time::{Duration, Instant};
thread_local! {
static PROFILE_DATA: RefCell<FxHashMap<&'static str, (u64, Duration)>> =
RefCell::new(FxHashMap::default());
}
pub struct ProfileScope {
name: &'static str,
start: Instant,
}
impl ProfileScope {
pub fn new(name: &'static str) -> Self {
Self {
name,
start: Instant::now(),
}
}
}
impl Drop for ProfileScope {
fn drop(&mut self) {
let duration = self.start.elapsed();
PROFILE_DATA.with(|data| {
let mut map = data.borrow_mut();
let entry = map.entry(self.name).or_insert((0, Duration::ZERO));
entry.0 += 1;
entry.1 += duration;
});
}
}
pub fn output_profiling_data() {
use std::env;
use std::fs::File;
use std::io::{BufWriter, Write};
let file_path = match env::current_dir() {
Ok(mut path) => {
path.push("profiling_data.txt");
path
}
Err(_) => std::path::PathBuf::from("profiling_data.txt"),
};
let file = match File::create(&file_path) {
Ok(f) => f,
Err(e) => {
eprintln!("Failed to create profiling data file: {}", e);
return;
}
};
let mut writer = BufWriter::new(file);
PROFILE_DATA.with(|data| {
let map = data.borrow();
for (name, (count, duration)) in map.iter() {
let _ = writeln!(writer, "{}: {} calls, {:?} total", name, count, duration);
}
});
let mut k_tuple_data = crate::analysis::k_tuples::profiling::snapshot();
if !k_tuple_data.is_empty() {
k_tuple_data.sort_by(|a, b| b.2.cmp(&a.2));
let total = k_tuple_data
.iter()
.fold(Duration::ZERO, |acc, (_, _, d)| acc.saturating_add(*d));
let _ = writeln!(writer, "k_tuples_hotspots:");
for (name, count, duration) in k_tuple_data {
let percent = if total.is_zero() {
0.0
} else {
duration.as_secs_f64() / total.as_secs_f64() * 100.0
};
let _ = writeln!(
writer,
" {}: {} calls, {:?} total, {:.2}%",
name, count, duration, percent
);
}
}
}
}