use crate::algorithms::test_properties::compute_properties;
use crate::arc::{Arc, ArcLabel, ArcStateId};
use crate::error::OpenFstError;
use crate::fst::Fst;
use crate::properties::{K_ERROR, K_FST_PROPERTIES, internal::compat_properties};
use crate::symbol_table::SymbolTable;
use crate::weight::Weight;
pub fn verify<A: Arc, F: Fst<A>>(fst: &F, allow_negative_labels: bool) -> Result<(), OpenFstError> {
let fault = |message: String| Err(OpenFstError::VerificationFailed(message));
let nstates = fst.count_states();
match fst.start() {
None => {
if nstates > 0 {
return fault("FST start state ID not set".to_string());
}
}
Some(start) if start < A::StateId::from_usize(0) || start.as_usize() >= nstates => {
return fault(format!(
"FST start state ID out of valid range: [0, {nstates})"
));
}
Some(_) => {}
}
let isymbols = fst.input_symbols();
let osymbols = fst.output_symbols();
for state in fst.states() {
for (position, arc) in fst.arcs(state).enumerate() {
let at = || format!("of arc at position {position} of state {state:?}");
if !allow_negative_labels && arc.ilabel() < A::Label::epsilon() {
return fault(format!("FST input label ID {} is negative", at()));
}
if let Some(table) = &isymbols
&& !contains(table, arc.ilabel())
{
return fault(format!(
"FST input label ID {} {} is missing from input symbol table \"{}\"",
arc.ilabel(),
at(),
table.name()
));
}
if !allow_negative_labels && arc.olabel() < A::Label::epsilon() {
return fault(format!("FST output label ID {} is negative", at()));
}
if let Some(table) = &osymbols
&& !contains(table, arc.olabel())
{
return fault(format!(
"FST output label ID {} {} is missing from output symbol table \"{}\"",
arc.olabel(),
at(),
table.name()
));
}
if !arc.weight().is_member() {
return fault(format!("FST weight {} is invalid", at()));
}
if arc.nextstate() < A::StateId::from_usize(0) {
return fault(format!("FST destination state ID {} is negative", at()));
}
if arc.nextstate().as_usize() >= nstates {
return fault(format!(
"FST destination state ID {} exceeds number of states",
at()
));
}
}
if !fst.final_weight(state).is_member() {
return fault(format!("FST final weight of state {state:?} is invalid"));
}
}
let stored = fst.properties(K_FST_PROPERTIES, false);
if stored & K_ERROR != 0 {
return fault("FST error property is set".to_string());
}
if !compat_properties(stored, compute_properties(fst, K_FST_PROPERTIES).props) {
return fault("stored FST properties contradict its contents".to_string());
}
Ok(())
}
fn contains<L: ArcLabel>(table: &SymbolTable, label: L) -> bool {
match label.to_i64() {
Some(label) => table.member_key(label),
None => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AtomicRc;
use crate::arc::StdArc;
use crate::fst::MutableFst;
use crate::fsts::vector_fst::StdVectorFst;
use crate::properties::{K_ACCEPTOR, K_NOT_ACCEPTOR};
use crate::weights::float_weight::TropicalWeight;
fn chain() -> StdVectorFst {
let mut fst = StdVectorFst::new();
fst.add_state();
fst.add_state();
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight::one(), 1));
fst.set_final(1, TropicalWeight::one());
fst
}
fn message(fst: &StdVectorFst, allow_negative: bool) -> String {
match verify(fst, allow_negative) {
Ok(()) => panic!("expected a fault"),
Err(e) => e.to_string(),
}
}
#[test]
fn a_well_formed_fst_passes() {
verify(&chain(), false).unwrap();
}
#[test]
fn a_missing_start_state_is_a_fault_only_once_there_are_states() {
let empty = StdVectorFst::new();
assert_eq!(empty.start(), None);
verify(&empty, false).unwrap();
let mut fst = StdVectorFst::new();
fst.add_state();
assert_eq!(fst.start(), None);
assert!(message(&fst, false).contains("start state ID not set"));
}
#[test]
fn a_start_state_outside_the_fst_is_a_fault() {
let mut fst = chain();
fst.set_start(7);
assert!(message(&fst, false).contains("out of valid range"));
let mut fst = chain();
fst.set_start(-3);
assert!(message(&fst, false).contains("out of valid range"));
}
#[test]
fn an_arc_leading_to_a_negative_state_is_a_fault() {
let mut fst = chain();
fst.add_arc(1, StdArc::new(1, 1, TropicalWeight::one(), -5));
assert!(message(&fst, false).contains("is negative"));
}
#[test]
fn an_arc_leading_outside_the_fst_is_a_fault() {
let mut fst = chain();
fst.add_arc(1, StdArc::new(1, 1, TropicalWeight::one(), 9));
assert!(message(&fst, false).contains("exceeds number of states"));
}
#[test]
fn negative_labels_are_refused_unless_allowed() {
let mut fst = chain();
fst.add_arc(1, StdArc::new(-2, 1, TropicalWeight::one(), 1));
assert!(message(&fst, false).contains("input label ID"));
verify(&fst, true).unwrap();
let mut fst = chain();
fst.add_arc(1, StdArc::new(1, -2, TropicalWeight::one(), 1));
assert!(message(&fst, false).contains("output label ID"));
verify(&fst, true).unwrap();
}
#[test]
fn a_weight_outside_the_semiring_is_a_fault() {
let mut fst = chain();
fst.add_arc(1, StdArc::new(1, 1, TropicalWeight(f32::NAN), 1));
assert!(message(&fst, false).contains("weight of arc"));
let mut fst = chain();
fst.set_final(0, TropicalWeight(f32::NAN));
assert!(message(&fst, false).contains("final weight of state"));
}
#[test]
fn a_label_missing_from_its_symbol_table_is_a_fault() {
let mut table = SymbolTable::new("input".to_string());
table.add_symbol("<eps>", 0);
table.add_symbol("a", 1);
let mut fst = chain();
fst.set_input_symbols(Some(AtomicRc::new(table.clone())));
verify(&fst, false).unwrap();
fst.add_arc(1, StdArc::new(2, 2, TropicalWeight::one(), 1));
assert!(message(&fst, false).contains("missing from input symbol table"));
let mut fst = chain();
fst.set_output_symbols(Some(AtomicRc::new(table)));
fst.add_arc(1, StdArc::new(1, 2, TropicalWeight::one(), 1));
assert!(message(&fst, false).contains("missing from output symbol table"));
}
#[test]
fn property_bits_that_contradict_the_contents_are_a_fault() {
let mut fst = chain();
fst.set_properties(K_NOT_ACCEPTOR, K_ACCEPTOR | K_NOT_ACCEPTOR);
assert!(message(&fst, false).contains("stored FST properties contradict"));
let mut fst = chain();
fst.set_properties(0, K_ACCEPTOR | K_NOT_ACCEPTOR);
verify(&fst, false).unwrap();
}
#[test]
fn an_fst_carrying_the_error_bit_is_a_fault() {
let mut fst = chain();
fst.set_properties(K_ERROR, K_ERROR);
assert!(message(&fst, false).contains("error property is set"));
}
}