use crate::KTuples;
use anyhow::{Result, bail};
use parol_runtime::TerminalIndex;
use parol_runtime::log::trace;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fmt::{Display, Error, Formatter};
pub type StateIndex = usize;
pub type ProductionIndex = usize;
pub type CompiledProductionIndex = i32;
pub const INVALID_PROD: CompiledProductionIndex = -1;
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct DFAState {
pub id: StateIndex,
pub prod_num: CompiledProductionIndex,
}
impl DFAState {
pub(crate) fn is_accepting(&self) -> bool {
self.prod_num >= 0
}
}
impl Display for DFAState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
let prod_num = if self.prod_num == INVALID_PROD {
"".to_owned()
} else {
format!(", Pr({})", self.prod_num)
};
let accepted = if self.is_accepting() {
", accepting"
} else {
""
};
write!(f, "Id({}{}){}", self.id, accepted, prod_num)
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct LookaheadDFA {
pub states: Vec<DFAState>,
pub transitions: BTreeMap<StateIndex, BTreeMap<TerminalIndex, StateIndex>>,
pub k: usize,
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum TransitionInfo {
NoTransition,
OtherTransitions,
TransitionExists(StateIndex),
}
impl LookaheadDFA {
pub fn from_k_tuples(k_tuples: &KTuples, prod_num: usize) -> Self {
let mut dfa = Self {
states: vec![DFAState {
id: 0,
prod_num: if k_tuples.is_empty() {
prod_num as i32
} else {
INVALID_PROD
},
}],
transitions: BTreeMap::new(),
k: 0,
};
trace!("KTuples for production {prod_num}");
for k_tuple in &k_tuples.sorted() {
trace!("{k_tuple:?}");
let mut current_state = 0;
if !k_tuple.is_eps() {
let tuple = k_tuple.terminals();
for ti in tuple.iter() {
current_state = dfa.add_transition(current_state, ti);
}
dfa.k = std::cmp::max(dfa.k, k_tuple.len());
}
dfa.states[current_state].prod_num = prod_num as i32;
}
dfa
}
fn add_transition(&mut self, from_state: StateIndex, terminal: TerminalIndex) -> StateIndex {
match self.transition_info(from_state, terminal) {
TransitionInfo::TransitionExists(to_state) => to_state,
TransitionInfo::OtherTransitions => {
let to_state = self.new_state();
let transitions_from_state = self.transitions.get_mut(&from_state).unwrap();
transitions_from_state.insert(terminal, to_state);
to_state
}
TransitionInfo::NoTransition => {
let to_state = self.new_state();
let mut transitions_from_state = BTreeMap::new();
transitions_from_state.insert(terminal, to_state);
self.transitions.insert(from_state, transitions_from_state);
to_state
}
}
}
pub fn union(&self, other: &Self) -> Result<Self> {
self.clone().unite(other)
}
pub fn unite(self, other: &Self) -> Result<Self> {
let state_mapping: RefCell<BTreeMap<StateIndex, StateIndex>> =
RefCell::new(BTreeMap::new());
state_mapping.borrow_mut().insert(0, 0);
let result_union = RefCell::new(self);
loop {
let mut changed = false;
for tr in &other.transitions {
if state_mapping.borrow().contains_key(tr.0) {
let result_state_index = *state_mapping.borrow().get(tr.0).unwrap();
for (terminal, to_state) in tr.1 {
let result_state = result_union
.borrow_mut()
.add_transition(result_state_index, *terminal);
if state_mapping
.borrow_mut()
.insert(*to_state, result_state)
.is_none()
{
changed = true;
let other_state_accepted = other.states[*to_state].is_accepting();
let other_to_state_prod_num = other.states[*to_state].prod_num;
let result_state_accepted =
result_union.borrow().states[result_state].is_accepting();
let result_state_prod_num =
result_union.borrow().states[result_state].prod_num;
if other_state_accepted
&& result_state_accepted
&& (other_to_state_prod_num != result_state_prod_num)
{
let message = format!(
r#"Conflict in union operation detected:
Ambiguous production number prediction
{result_state_prod_num} <--> {other_to_state_prod_num}"#
);
bail!(message);
}
result_union
.borrow_mut()
.coin_state(result_state, other_to_state_prod_num);
}
}
}
}
if !changed {
break;
}
}
Ok(result_union.into_inner())
}
fn new_state(&mut self) -> StateIndex {
let id = self.states.len();
self.states.push(DFAState {
id,
prod_num: INVALID_PROD,
});
id
}
fn coin_state(&mut self, id: StateIndex, prod_num: CompiledProductionIndex) {
self.states[id].prod_num = prod_num;
}
fn transition_info(&self, from_state: StateIndex, terminal: TerminalIndex) -> TransitionInfo {
let transitions_from_state_exist = self.transitions.contains_key(&from_state);
let transition_from_state_via_terminal_exists = if transitions_from_state_exist {
self.transitions
.get(&from_state)
.unwrap()
.contains_key(&terminal)
} else {
false
};
match (
transitions_from_state_exist,
transition_from_state_via_terminal_exists,
) {
(true, true) => TransitionInfo::TransitionExists(
*self
.transitions
.get(&from_state)
.unwrap()
.get(&terminal)
.unwrap(),
),
(true, false) => TransitionInfo::OtherTransitions,
_ => TransitionInfo::NoTransition,
}
}
}
impl Display for LookaheadDFA {
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
let states = self
.states
.iter()
.map(|s| format!(" {s}"))
.collect::<Vec<String>>()
.join("\n");
let transitions = self
.transitions
.iter()
.map(|(s, t)| {
let ts = t
.iter()
.map(|(terminal, to_state)| format!(" => {terminal} => {to_state}"))
.collect::<Vec<String>>()
.join("\n");
format!(" {s}\n{ts}")
})
.collect::<Vec<String>>()
.join("\n");
write!(
f,
"States\n{}\nTransitions:\n{}\nk:{}\n",
states, transitions, self.k
)
}
}