use std::collections::BTreeMap;
use std::ops::RangeInclusive;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum State<T> {
Normal,
Action(T),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct CharRange {
pub start: char,
pub end: char,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NodeKind {
Fork,
Link,
Leaf,
Sink,
Terminal,
}
impl CharRange {
pub const MIN: Self = CharRange { start: '\0', end: '\0' };
pub const MAX: Self = CharRange { start: std::char::MAX, end: std::char::MAX };
pub fn new(start: char, end: char) -> Self {
assert!(start <= end);
CharRange { start, end }
}
fn contains(self, ch: char) -> bool {
ch >= self.start && ch <= self.end
}
pub(crate) fn concat(self, other: CharRange) -> Option<CharRange> {
use core::cmp::{max, min};
let (intersect_start, intersect_end) = (
max(self.start as u32, other.start as u32),
min(self.end as u32, other.end as u32).saturating_add(1)
);
if intersect_start > intersect_end { return None }
Some(CharRange {
start: min(self.start, other.start),
end: max(self.end, other.end),
})
}
}
fn state_range(state: usize) -> RangeInclusive<(usize, CharRange)> {
let min = CharRange::MIN;
let max = CharRange::MAX;
(state, min)..=(state, max)
}
#[derive(Debug, Clone)]
pub struct Automata<T> {
pub states: Vec<State<T>>,
pub edges: BTreeMap<(usize, CharRange), usize>,
}
impl<T> Automata<T> {
pub(crate) fn new(inital: State<T>) -> Self {
Automata {
states: vec![inital],
edges: BTreeMap::new(),
}
}
pub fn get(&self, ix: usize) -> Option<&State<T>> {
self.states.get(ix)
}
pub fn insert_state(&mut self, state: State<T>) {
self.states.push(state)
}
pub fn insert_edge(&mut self, from: usize, to: usize, range: CharRange) {
self.edges.insert((from, range), to);
}
pub fn transitions_from(&self, state: usize) ->
impl Iterator<Item=(&CharRange, usize)>
{
self.edges.range(state_range(state))
.map(|(k, &v)| (&k.1, v))
}
pub fn transite(&self, state: usize, ch: char) -> Option<usize> {
self.transitions_from(state)
.find(|t| t.0.contains(ch))
.map(|(_, state)| state)
}
pub fn find_terminal_node(&self) -> usize {
for i in 0..self.states.len() {
let ends: Vec<_> = self.transitions_from(i)
.filter(|(&ch, _)| ch.start == '\0' && ch.end == std::char::MAX)
.map(|(_, end)| end)
.collect();
if ends.len() == 1 { return ends[0] }
}
panic!("Automata without a terminal state")
}
pub fn node_kinds(&self) -> Vec<NodeKind> {
let terminal = self.find_terminal_node();
let coedges = self.edges.iter().map(|(&(start, range), &end)| (end, range, start));
(0..self.states.len()).map(|i| {
if i == terminal {
return NodeKind::Terminal;
}
let far_edges = self.transitions_from(i)
.filter(|&(_, end)| end != terminal);
let far_coedges = coedges.clone()
.filter(|(end, _, start)| *end == i && *start != i);
if far_coedges.count() > 1 {
NodeKind::Sink
}
else {
match far_edges.count() {
0 => NodeKind::Leaf,
1 => NodeKind::Link,
_ => NodeKind::Fork,
}
}
})
.collect()
}
}