use crate::{Action, ParseTable};
use adze_ir::{Associativity, Grammar, PrecedenceKind, SymbolId};
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct ConflictStats {
pub shift_reduce_conflicts: usize,
pub reduce_reduce_conflicts: usize,
pub precedence_resolved: usize,
pub associativity_resolved: usize,
pub explicit_glr: usize,
pub default_resolved: usize,
}
pub struct ConflictAnalyzer {
stats: ConflictStats,
}
impl Default for ConflictAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl ConflictAnalyzer {
pub fn new() -> Self {
Self {
stats: ConflictStats::default(),
}
}
pub fn analyze_table(&mut self, _table: &ParseTable) -> ConflictStats {
self.stats = ConflictStats::default();
self.stats.clone()
}
#[allow(dead_code)]
fn categorize_conflicts(&mut self, actions: &[Action]) {
let shifts = actions
.iter()
.filter(|a| matches!(a, Action::Shift(_)))
.count();
let reduces = actions
.iter()
.filter(|a| matches!(a, Action::Reduce(_)))
.count();
if shifts > 0 && reduces > 0 {
self.stats.shift_reduce_conflicts += shifts * reduces;
}
if reduces > 1 {
self.stats.reduce_reduce_conflicts += reduces * (reduces - 1) / 2;
}
}
pub fn get_stats(&self) -> &ConflictStats {
&self.stats
}
}
pub struct PrecedenceResolver {
token_precedences: HashMap<SymbolId, (i16, Associativity)>,
symbol_precedences: HashMap<SymbolId, (i16, Associativity)>,
}
impl PrecedenceResolver {
pub fn new(grammar: &Grammar) -> Self {
let mut token_precedences = HashMap::new();
let mut symbol_precedences = HashMap::new();
for prec in &grammar.precedences {
for &symbol in &prec.symbols {
token_precedences.insert(symbol, (prec.level, prec.associativity));
}
}
for (symbol_id, rules) in &grammar.rules {
for rule in rules {
if let Some(PrecedenceKind::Static(level)) = &rule.precedence
&& let Some(assoc) = rule.associativity
{
symbol_precedences.insert(*symbol_id, (*level, assoc));
}
}
}
Self {
token_precedences,
symbol_precedences,
}
}
pub fn can_resolve_shift_reduce(
&self,
shift_symbol: SymbolId,
reduce_symbol: SymbolId,
) -> Option<PrecedenceDecision> {
let shift_prec = self.token_precedences.get(&shift_symbol)?;
let reduce_prec = self.symbol_precedences.get(&reduce_symbol)?;
if shift_prec.0 > reduce_prec.0 {
Some(PrecedenceDecision::PreferShift)
} else if reduce_prec.0 > shift_prec.0 {
Some(PrecedenceDecision::PreferReduce)
} else {
match reduce_prec.1 {
Associativity::Left => Some(PrecedenceDecision::PreferReduce),
Associativity::Right => Some(PrecedenceDecision::PreferShift),
Associativity::None => Some(PrecedenceDecision::Error),
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrecedenceDecision {
PreferShift,
PreferReduce,
Error,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Action, LexMode, ParseTable, StateId};
use adze_ir::{
Associativity, Grammar, Precedence, PrecedenceKind, ProductionId, Rule, Symbol, SymbolId,
};
use std::collections::BTreeMap;
#[test]
fn test_conflict_analyzer() {
let table = ParseTable {
action_table: vec![vec![vec![Action::Shift(StateId(1))]]],
goto_table: vec![],
symbol_metadata: vec![],
state_count: 1,
symbol_count: 1,
symbol_to_index: BTreeMap::new(),
index_to_symbol: vec![],
external_scanner_states: vec![],
rules: vec![],
nonterminal_to_index: BTreeMap::new(),
goto_indexing: crate::GotoIndexing::NonterminalMap,
eof_symbol: SymbolId(0),
start_symbol: SymbolId(1),
grammar: Grammar::new("test".to_string()),
initial_state: StateId(0),
token_count: 1,
external_token_count: 0,
lex_modes: vec![
LexMode {
lex_state: 0,
external_lex_state: 0
};
1
],
extras: vec![],
dynamic_prec_by_rule: vec![],
rule_assoc_by_rule: vec![],
alias_sequences: vec![],
field_names: vec![],
field_map: BTreeMap::new(),
};
let mut analyzer = ConflictAnalyzer::new();
let stats = analyzer.analyze_table(&table);
assert_eq!(stats.shift_reduce_conflicts, 0);
assert_eq!(stats.reduce_reduce_conflicts, 0);
}
#[test]
fn test_precedence_resolver() {
let mut grammar = Grammar::new("test".to_string());
grammar.precedences.push(Precedence {
level: 1,
associativity: Associativity::Left,
symbols: vec![SymbolId(1)],
});
grammar.precedences.push(Precedence {
level: 2,
associativity: Associativity::Right,
symbols: vec![SymbolId(2)],
});
grammar.rules.insert(
SymbolId(3),
vec![Rule {
lhs: SymbolId(3),
rhs: vec![Symbol::Terminal(SymbolId(1))],
precedence: Some(PrecedenceKind::Static(1)),
associativity: Some(Associativity::Left),
fields: vec![],
production_id: ProductionId(0),
}],
);
let resolver = PrecedenceResolver::new(&grammar);
let decision = resolver.can_resolve_shift_reduce(SymbolId(2), SymbolId(3));
assert_eq!(decision, Some(PrecedenceDecision::PreferShift));
let decision = resolver.can_resolve_shift_reduce(SymbolId(1), SymbolId(3));
assert_eq!(decision, Some(PrecedenceDecision::PreferReduce));
}
}