//! This file defines the behaviour of the Atomic languages, and //! provides a default implementation. //! //! Here I do not to substitute external packages' implementations in //! the future, so why define a trait for the atomic languages? //! Because this way I can easily substitute other implementations if //! I have better ideas in the future. use grammar::{Error as GrammarError, Grammar, TNT}; use nfa::{DOption, LabelType, Nfa}; use std::ops::Deref; /// The expected behaviours of an atomic language. pub trait Atom: Nfa> + Deref { /// Return the index of a node representing the derivative of the /// left-linear null closure of `nt` with respect to `t`. fn atom(&self, nt: usize, t: usize) -> Result, GrammarError>; /// A type that iterates through the rule positions. type Iter<'a>: Iterator + 'a where Self: 'a; /// Return an iterator of rule positions responsible for an edge /// from the virtual node corresponding to the non-terminal `nt` /// and terminal `t` to `target`. fn trace(&self, nt: usize, t: usize, target: usize) -> Option<::Iter<'_>>; /// Return the index of the empty state. fn empty(&self) -> usize; /// Tell whether a node is accepting. fn is_accepting(&self, node_id: usize) -> Result; /// Return the bound for `is_accepting`. fn accepting_len(&self) -> usize; } pub mod default; pub use default::DefaultAtom; #[cfg(test)] mod tests { use super::*; use grammar::test_grammar_helper::*; #[cfg(feature = "test-print-viz")] use graph::Graph; #[test] fn atom() -> Result<(), Box> { let grammar = new_notes_grammar()?; let atom = DefaultAtom::from_grammar(grammar)?; println!("atom = {atom}"); #[cfg(feature = "test-print-viz")] { println!("virtual frag for 1, 3: "); for (index, frag) in atom .generate_virtual_frags(1, 3, None) .iter() .flatten() .enumerate() { crate::item::default::print_labels(&atom, *frag)?; frag.print_viz(&format!("frag {index}.gv"))?; } } Ok(()) } }