arcweight 0.3.0

A high-performance, modular library for weighted finite state transducers with comprehensive examples and benchmarks
Documentation
//! # Macros
//!
//! Convenience macros for FST construction.
//!
//! ## Overview
//!
//! This module provides declarative macros that simplify the construction of
//! FSTs by allowing inline specification of states, arcs, and properties.
//! These macros reduce boilerplate compared to imperative construction while
//! maintaining type safety.
//!
//! ## Available Macros
//!
//! | Macro | Purpose |
//! |-------|---------|
//! | `fst!` | Create a complete [`VectorFst`] with states and arcs |
//! | `symt!` | Create a [`SymbolTable`] from symbol strings |
//! | `fst_path!` | Create an [`FstPath`] for path representation |
//!
//! ## Examples
//!
//! ### Building an FST
//!
//! ```
//! use arcweight::fst;
//! use arcweight::prelude::*;
//!
//! // Create a simple two-state acceptor
//! let acceptor = fst!(
//!     start: 0,
//!     finals: [1],
//!     arcs: [
//!         (0, 1, 1, 1, 0.5),
//!     ]
//! );
//!
//! assert_eq!(acceptor.num_states(), 2);
//! ```
//!
//! ### Creating Symbol Tables
//!
//! ```
//! use arcweight::symt;
//!
//! let symbols = symt!["a", "b", "c"];
//! assert_eq!(symbols.find_id("a"), Some(1));
//! ```
//!
//! [`VectorFst`]: crate::fst::VectorFst
//! [`SymbolTable`]: crate::utils::SymbolTable
//! [`FstPath`]: crate::utils::FstPath

/// Creates a [`VectorFst<TropicalWeight>`] from a declarative specification.
///
/// This macro provides a concise syntax for constructing FSTs by specifying
/// the start state, final states, and arcs in a structured format.
///
/// # Syntax
///
/// ```text
/// fst!(
///     start: <state_id>,
///     finals: [<state_id>, ...],
///     arcs: [
///         (<from>, <to>, <ilabel>, <olabel>, <weight>),
///         ...
///     ]
/// )
/// ```
///
/// # Arguments
///
/// * `start` - The initial state ID (states are created automatically)
/// * `finals` - A list of final state IDs (all receive weight `TropicalWeight::one()`)
/// * `arcs` - A list of arc tuples: `(from_state, to_state, input_label, output_label, weight)`
///
/// # Returns
///
/// A [`VectorFst<TropicalWeight>`] containing all specified states and arcs.
///
/// # Examples
///
/// Basic acceptor:
///
/// ```
/// use arcweight::fst;
/// use arcweight::prelude::*;
///
/// let fst = fst!(
///     start: 0,
///     finals: [1],
///     arcs: [
///         (0, 1, 1, 1, 1.0),
///     ]
/// );
///
/// assert_eq!(fst.num_states(), 2);
/// assert_eq!(fst.start(), Some(0));
/// assert!(fst.is_final(1));
/// ```
///
/// Multi-path transducer:
///
/// ```
/// use arcweight::fst;
/// use arcweight::prelude::*;
///
/// let fst = fst!(
///     start: 0,
///     finals: [2, 3],
///     arcs: [
///         (0, 1, 1, 10, 0.5),  // Transform 1 -> 10
///         (1, 2, 2, 20, 0.3),  // Transform 2 -> 20
///         (1, 3, 3, 30, 0.7),  // Transform 3 -> 30
///     ]
/// );
///
/// assert_eq!(fst.num_states(), 4);
/// ```
///
/// [`VectorFst<TropicalWeight>`]: crate::fst::VectorFst
#[macro_export]
macro_rules! fst {
    (
        start: $start:expr,
        finals: [$($final:expr),* $(,)?],
        arcs: [$(
            ($from:expr, $to:expr, $ilabel:expr, $olabel:expr, $weight:expr)
        ),* $(,)?]
    ) => {{
        let mut fst = $crate::fst::VectorFst::<$crate::semiring::TropicalWeight>::new();

        // Find max state ID
        let mut max_state = $start;
        $(
            if $final > max_state {
                max_state = $final;
            }
        )*
        $(
            if $from > max_state {
                max_state = $from;
            }
            if $to > max_state {
                max_state = $to;
            }
        )*

        // Add states
        for _ in 0..=max_state {
            fst.add_state();
        }

        // Set start
        fst.set_start($start);

        // Set finals
        $(
            fst.set_final($final, $crate::semiring::TropicalWeight::one());
        )*

        // Add arcs
        $(
            fst.add_arc($from, $crate::arc::Arc::new(
                $ilabel,
                $olabel,
                $crate::semiring::TropicalWeight::new($weight),
                $to
            ));
        )*

        fst
    }};
}

/// Creates a [`SymbolTable`] from a list of symbol strings.
///
/// Symbol IDs are assigned sequentially starting from 1, with ID 0 reserved
/// for epsilon (the empty symbol). This is consistent with OpenFST conventions.
///
/// # Arguments
///
/// * `symbols` - A comma-separated list of string literals or expressions
///
/// # Returns
///
/// A [`SymbolTable`] with symbols assigned sequential IDs starting from 1.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// use arcweight::symt;
///
/// let symbols = symt!["hello", "world", "test"];
///
/// assert_eq!(symbols.find_id("hello"), Some(1));
/// assert_eq!(symbols.find_id("world"), Some(2));
/// assert_eq!(symbols.find_id("test"), Some(3));
/// assert_eq!(symbols.find_key(1), Some("hello"));
/// ```
///
/// With trailing comma:
///
/// ```
/// use arcweight::symt;
///
/// let symbols = symt![
///     "cat",
///     "dog",
///     "bird",
/// ];
///
/// // Note: len() includes the epsilon symbol at index 0
/// assert_eq!(symbols.len(), 4);
/// ```
///
/// [`SymbolTable`]: crate::utils::SymbolTable
#[macro_export]
macro_rules! symt {
    [$($symbol:expr),* $(,)?] => {{
        let mut table = $crate::utils::SymbolTable::new();
        $(
            table.add_symbol($symbol);
        )*
        table
    }};
}

/// Creates an [`FstPath`] representing a path through an FST.
///
/// This macro constructs a path object containing the sequence of states,
/// arcs traversed, and total path weight. Useful for testing and representing
/// specific paths through an FST.
///
/// # Syntax
///
/// ```text
/// fst_path!(
///     states: [<state_id>, ...],
///     arcs: [
///         (<ilabel>, <olabel>, <weight>),
///         ...
///     ],
///     weight: <total_weight>
/// )
/// ```
///
/// # Arguments
///
/// * `states` - Ordered list of state IDs in the path
/// * `arcs` - Arc labels and weights for each transition (one fewer than states)
/// * `weight` - Total accumulated weight of the path
///
/// # Returns
///
/// An [`FstPath<TropicalWeight>`] containing the specified path information.
///
/// # Examples
///
/// Simple path:
///
/// ```
/// use arcweight::fst_path;
/// use arcweight::prelude::*;
///
/// let path = fst_path!(
///     states: [0, 1, 2],
///     arcs: [
///         (1, 1, 1.0),
///         (2, 2, 0.5),
///     ],
///     weight: 1.5
/// );
///
/// assert_eq!(path.states.len(), 3);
/// assert_eq!(path.arcs.len(), 2);
/// assert_eq!(path.final_state, 2);
/// ```
///
/// Single-arc path:
///
/// ```
/// use arcweight::fst_path;
/// use arcweight::prelude::*;
///
/// let path = fst_path!(
///     states: [0, 1],
///     arcs: [
///         (10, 20, 0.25),
///     ],
///     weight: 0.25
/// );
///
/// assert_eq!(path.arcs[0].ilabel, 10);
/// assert_eq!(path.arcs[0].olabel, 20);
/// ```
///
/// [`FstPath`]: crate::utils::FstPath
/// [`FstPath<TropicalWeight>`]: crate::utils::FstPath
#[macro_export]
macro_rules! fst_path {
    (
        states: [$($state:expr),* $(,)?],
        arcs: [$(
            ($ilabel:expr, $olabel:expr, $weight:expr)
        ),* $(,)?],
        weight: $total_weight:expr
    ) => {{
        {
            let states_vec = vec![$($state),*];
            let mut arcs_vec = Vec::new();
            let states_slice = &states_vec[..];
            let mut arc_idx = 0;
            $(
                if arc_idx + 1 < states_slice.len() {
                    arcs_vec.push($crate::arc::Arc::new(
                        $ilabel,
                        $olabel,
                        $crate::semiring::TropicalWeight::new($weight),
                        states_slice[arc_idx + 1]
                    ));
                }
                arc_idx += 1;
            )*
            let final_state = *states_vec.last().unwrap_or(&0);
            $crate::utils::FstPath {
                states: states_vec,
                arcs: arcs_vec,
                weight: $crate::semiring::TropicalWeight::new($total_weight),
                final_state,
            }
        }
    }};
}