piper-phoneme-streaming 0.1.1

A high-performance Rust library for streaming Text-to-Phoneme (G2P) conversion.
Documentation
use crate::text_expand::{ExpandResult, ExpandTask, ExpandUnit};
use std::collections::VecDeque;

pub struct EnPercentExpandTask;

impl ExpandTask for EnPercentExpandTask {
    fn expand(&self, queue: &VecDeque<ExpandUnit>) -> Option<ExpandResult> {
        if queue.len() < 2 {
            if let Some(ExpandUnit::Number(_)) = queue.front() {
                return Some(ExpandResult::Maybe);
            }
            return None;
        }

        match (queue.front(), queue.get(1)) {
            (Some(ExpandUnit::Number(n)), Some(ExpandUnit::Mark('%'))) => {
                // Replace Number('%') with [Number(n), Word("percent")].
                // The Number will be further expanded by EnNumberExpandTask recursively.
                Some(ExpandResult::Replace(
                    2,
                    vec![
                        ExpandUnit::Number(n.clone()),
                        ExpandUnit::Word("percent".into()),
                    ],
                ))
            }
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_en_percent_expansion() {
        let task = EnPercentExpandTask;
        let mut queue = VecDeque::new();
        queue.push_back(ExpandUnit::Number("50".into()));
        queue.push_back(ExpandUnit::Mark('%'));

        let res = task.expand(&queue).unwrap();
        if let ExpandResult::Replace(n, units) = res {
            assert_eq!(n, 2);
            assert_eq!(units[0], ExpandUnit::Number("50".into()));
            assert_eq!(units[1], ExpandUnit::Word("percent".into()));
        } else {
            panic!("Expected Replace result");
        }
    }

    #[test]
    fn test_en_percent_zero() {
        // "0%" should expand as [Number("0"), Word("percent")]
        let task = EnPercentExpandTask;
        let mut queue = VecDeque::new();
        queue.push_back(ExpandUnit::Number("0".into()));
        queue.push_back(ExpandUnit::Mark('%'));

        let res = task.expand(&queue).unwrap();
        if let ExpandResult::Replace(n, units) = res {
            assert_eq!(n, 2);
            assert_eq!(units[0], ExpandUnit::Number("0".into()));
            assert_eq!(units[1], ExpandUnit::Word("percent".into()));
        } else {
            panic!("Expected Replace result for 0%");
        }
    }

    #[test]
    fn test_en_percent_maybe_single_number() {
        // Single Number in queue — should return Maybe
        let task = EnPercentExpandTask;
        let mut queue = VecDeque::new();
        queue.push_back(ExpandUnit::Number("50".into()));

        let res = task.expand(&queue);
        assert!(
            matches!(res, Some(ExpandResult::Maybe)),
            "Single number should return Maybe"
        );
    }

    #[test]
    fn test_en_percent_non_percent_mark_returns_none() {
        // Number followed by non-% mark should return None
        let task = EnPercentExpandTask;
        let mut queue = VecDeque::new();
        queue.push_back(ExpandUnit::Number("50".into()));
        queue.push_back(ExpandUnit::Mark('.'));

        let res = task.expand(&queue);
        assert!(
            res.is_none(),
            "Number followed by '.' should not expand as percent"
        );
    }

    #[test]
    fn test_en_percent_non_number_returns_none() {
        let task = EnPercentExpandTask;
        let mut queue = VecDeque::new();
        queue.push_back(ExpandUnit::Word("hello".into()));

        let res = task.expand(&queue);
        assert!(res.is_none(), "Non-number input should return None");
    }
}