use crate::AtomicRc;
use crate::arc::Arc;
use crate::error::OpenFstError;
use crate::fst_header::{FstHeader, flags};
use crate::properties::K_EXPANDED;
use crate::symbol_table::SymbolTable;
use std::fmt;
use std::fs::File;
use std::io::{BufReader, Read, Write};
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
pub const FST_MAGIC_NUMBER: i32 = 2125659606;
pub const NO_LABEL: i32 = -1;
pub const NO_STATE_ID: i32 = -1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatchType {
Input = 1,
Output = 2,
Both = 3,
None = 4,
Unknown = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileReadMode {
Read,
Map,
}
impl FileReadMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Read => "read",
Self::Map => "map",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"read" => Some(Self::Read),
"map" => Some(Self::Map),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct FstReadOptions {
pub source: String,
pub header: Option<FstHeader>,
pub isymbols: Option<AtomicRc<SymbolTable>>,
pub osymbols: Option<AtomicRc<SymbolTable>>,
pub mode: FileReadMode,
pub read_isymbols: bool,
pub read_osymbols: bool,
pub verify: bool,
}
impl Default for FstReadOptions {
fn default() -> Self {
Self {
source: "<unspecified>".to_string(),
header: None,
isymbols: None,
osymbols: None,
mode: FileReadMode::Read,
read_isymbols: true,
read_osymbols: true,
verify: true,
}
}
}
impl FstReadOptions {
pub fn new<S: Into<String>>(source: S) -> Self {
Self {
source: source.into(),
..Default::default()
}
}
pub fn mode(mut self, mode: FileReadMode) -> Self {
self.mode = mode;
self
}
pub fn read_symbols(mut self, read: bool) -> Self {
self.read_isymbols = read;
self.read_osymbols = read;
self
}
pub fn with_header(mut self, header: FstHeader) -> Self {
self.header = Some(header);
self
}
}
impl fmt::Display for FstReadOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let set_or_null = |present: bool| if present { "set" } else { "null" };
write!(
f,
"source: \"{}\" mode: \"{}\" read_isymbols: \"{}\" \
read_osymbols: \"{}\" header: \"{}\" isymbols: \"{}\" \
osymbols: \"{}\" verify: \"{}\"",
self.source,
self.mode.as_str().to_uppercase(),
self.read_isymbols,
self.read_osymbols,
set_or_null(self.header.is_some()),
set_or_null(self.isymbols.is_some()),
set_or_null(self.osymbols.is_some()),
self.verify
)
}
}
#[derive(Debug, Clone)]
pub struct FstWriteOptions {
pub source: String,
pub write_header: bool,
pub write_isymbols: bool,
pub write_osymbols: bool,
pub align: bool,
pub stream_write: bool,
}
impl Default for FstWriteOptions {
fn default() -> Self {
Self {
source: "<unspecified>".to_string(),
write_header: true,
write_isymbols: true,
write_osymbols: true,
align: false,
stream_write: false,
}
}
}
#[derive(Debug, Default)]
pub struct PropertyCache(AtomicU64);
impl Clone for PropertyCache {
fn clone(&self) -> Self {
Self::new(self.get())
}
}
impl PartialEq for PropertyCache {
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}
impl Eq for PropertyCache {}
impl PropertyCache {
#[inline]
pub fn new(props: u64) -> Self {
Self(AtomicU64::new(props))
}
#[inline]
pub fn get(&self) -> u64 {
self.0.load(Ordering::Relaxed)
}
#[inline]
pub fn get_masked(&self, mask: u64) -> u64 {
self.get() & mask
}
#[inline]
pub fn set(&mut self, props: u64) {
*self.0.get_mut() = props;
}
#[inline]
pub fn modify(&mut self, f: impl FnOnce(u64) -> u64) {
let props = f(self.get());
self.set(props);
}
#[inline]
pub fn mark_error(&self) {
self.0
.fetch_or(crate::properties::K_ERROR, Ordering::Relaxed);
}
pub fn discover(&self, props: u64, mask: u64) {
let known = crate::properties::internal::known_properties(self.get() & mask);
let discovered = props & mask & !known;
if discovered != 0 {
self.0.fetch_or(discovered, Ordering::Relaxed);
}
}
}
pub trait Fst<A: Arc> {
type StateIter<'a>: Iterator<Item = A::StateId>
where
Self: 'a;
type ArcIter<'a>: Iterator<Item = A> + Clone
where
Self: 'a;
fn start(&self) -> Option<A::StateId>;
fn final_weight(&self, state: A::StateId) -> A::Weight;
fn num_arcs(&self, state: A::StateId) -> usize;
fn num_input_epsilons(&self, state: A::StateId) -> usize;
fn num_output_epsilons(&self, state: A::StateId) -> usize;
fn num_states_if_known(&self) -> Option<usize>;
fn properties(&self, mask: u64, test: bool) -> u64;
fn fst_type(&self) -> &str;
fn input_symbols(&self) -> Option<AtomicRc<SymbolTable>>;
fn output_symbols(&self) -> Option<AtomicRc<SymbolTable>>;
fn states<'a>(&'a self) -> Self::StateIter<'a>;
fn arcs<'a>(&'a self, state: A::StateId) -> Self::ArcIter<'a>;
fn count_states(&self) -> usize {
if let Some(n) = self.num_states_if_known() {
n
} else {
self.states().count()
}
}
fn count_arcs(&self) -> usize {
self.states().map(|state| self.num_arcs(state)).sum()
}
}
pub trait ExpandedFst<A: Arc>: Fst<A> {
fn num_states(&self) -> usize;
}
pub trait MutableFst<A: Arc>: ExpandedFst<A> {
fn set_start(&mut self, state: A::StateId);
fn set_final(&mut self, state: A::StateId, weight: A::Weight);
fn set_properties(&mut self, props: u64, mask: u64);
fn add_state(&mut self) -> A::StateId;
fn add_states(&mut self, n: usize);
fn add_arc(&mut self, state: A::StateId, arc: A);
fn arcs_mut(&mut self, state: A::StateId) -> &mut [A];
fn delete_arcs_n(&mut self, state: A::StateId, n: usize);
fn delete_arcs(&mut self, state: A::StateId);
fn delete_all_states(&mut self);
fn delete_states(&mut self, states: &[A::StateId]);
fn reserve_states(&mut self, n: usize);
fn reserve_arcs(&mut self, state: A::StateId, n: usize);
fn set_input_symbols(&mut self, syms: Option<AtomicRc<SymbolTable>>);
fn set_output_symbols(&mut self, syms: Option<AtomicRc<SymbolTable>>);
fn mutable_input_symbols(&mut self) -> Option<&mut SymbolTable>;
fn mutable_output_symbols(&mut self) -> Option<&mut SymbolTable>;
fn mutate_arcs<F>(&mut self, state: A::StateId, mutator: F)
where
F: FnMut(&mut A);
}
pub trait ContiguousArcsFst<A: Arc>: Fst<A> {
fn arcs_slice(&self, state: A::StateId) -> &[A];
}
#[inline]
pub fn count_states_slice<A: Arc, F: Fst<A>>(fsts: &[&F]) -> usize {
fsts.iter().map(|fst| fst.count_states()).sum()
}
#[inline(always)]
pub fn count_states<A: Arc, F: Fst<A>>(fst: &F) -> usize {
fst.count_states()
}
#[inline(always)]
pub fn count_arcs<A: Arc, F: Fst<A>>(fst: &F) -> usize {
fst.count_arcs()
}
#[derive(Debug, Clone)]
pub struct FstHeaderWithSymbols {
pub header: FstHeader,
pub isymbols: Option<AtomicRc<SymbolTable>>,
pub osymbols: Option<AtomicRc<SymbolTable>>,
}
pub fn read_fst_header<A: Arc, R: Read>(
reader: &mut R,
opts: &FstReadOptions,
fst_type: &str,
min_version: i32,
) -> Result<FstHeaderWithSymbols, OpenFstError> {
let header = match &opts.header {
Some(header) => header.clone(),
None => FstHeader::read(&mut *reader)?,
};
if header.fst_type != fst_type {
return Err(OpenFstError::InvalidFstHeader(format!(
"{}: FST not of type '{}', found '{}'",
opts.source, fst_type, header.fst_type
)));
}
let arc_type = A::type_name();
if header.arc_type != arc_type.as_str() {
return Err(OpenFstError::InvalidFstHeader(format!(
"{}: arc not of type '{}', found '{}'",
opts.source,
arc_type.as_str(),
header.arc_type
)));
}
if header.version < min_version {
return Err(OpenFstError::InvalidFstHeader(format!(
"{}: obsolete {} FST version {}, min_version={}",
opts.source, fst_type, header.version, min_version
)));
}
let mut isymbols = None;
if header.flags & flags::HAS_ISYMBOLS != 0 {
let table = SymbolTable::read(&mut *reader)?;
if opts.read_isymbols {
isymbols = Some(AtomicRc::new(table));
}
}
let mut osymbols = None;
if header.flags & flags::HAS_OSYMBOLS != 0 {
let table = SymbolTable::read(&mut *reader)?;
if opts.read_osymbols {
osymbols = Some(AtomicRc::new(table));
}
}
if opts.isymbols.is_some() {
isymbols = opts.isymbols.clone();
}
if opts.osymbols.is_some() {
osymbols = opts.osymbols.clone();
}
Ok(FstHeaderWithSymbols {
header,
isymbols,
osymbols,
})
}
pub fn write_fst_header<W: Write>(
writer: &mut W,
opts: &FstWriteOptions,
header: &FstHeader,
isymbols: Option<&SymbolTable>,
osymbols: Option<&SymbolTable>,
) -> Result<FstHeader, OpenFstError> {
let isymbols = isymbols.filter(|_| opts.write_isymbols);
let osymbols = osymbols.filter(|_| opts.write_osymbols);
let mut header = header.clone();
header.flags = 0;
if isymbols.is_some() {
header.flags |= flags::HAS_ISYMBOLS;
}
if osymbols.is_some() {
header.flags |= flags::HAS_OSYMBOLS;
}
if opts.align {
header.flags |= flags::IS_ALIGNED;
}
if opts.write_header {
header.write(&mut *writer)?;
}
if let Some(table) = isymbols {
table.write(&mut *writer)?;
}
if let Some(table) = osymbols {
table.write(&mut *writer)?;
}
Ok(header)
}
pub trait ReadFst<A: Arc>: Sized {
fn read_from_stream<R: Read>(
reader: &mut R,
opts: &FstReadOptions,
) -> Result<Self, OpenFstError>;
}
pub fn read_fst_from_file<A, F>(
path: impl AsRef<Path>,
opts: &FstReadOptions,
) -> Result<F, OpenFstError>
where
A: Arc,
F: Fst<A> + ExpandedFst<A> + ReadFst<A>,
{
let path = path.as_ref();
let mut reader = BufReader::new(File::open(path)?);
let header = FstHeader::read(&mut reader)?;
if (header.properties & K_EXPANDED) == 0 {
return Err(OpenFstError::InvalidFstHeader(format!(
"Not an ExpandedFst (K_EXPANDED property is missing): {:?}",
path
)));
}
let arc_type = A::type_name();
if header.arc_type != arc_type.as_str() {
return Err(OpenFstError::InvalidFstHeader(format!(
"Arc type mismatch. Expected '{}', got '{}'",
arc_type.as_str(),
header.arc_type
)));
}
let opts = FstReadOptions {
source: path.display().to_string(),
header: Some(header),
..opts.clone()
};
F::read_from_stream(&mut reader, &opts)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arc::StdArc;
use crate::weight::Weight;
use crate::weights::float_weight::TropicalWeight;
use std::io::Cursor;
fn symbols(name: &str, first: &str) -> SymbolTable {
let mut table = SymbolTable::new(name.to_string());
table.add_symbol("<eps>", 0);
table.add_symbol(first, 1);
table
}
fn header() -> FstHeader {
FstHeader {
fst_type: "vector".to_string(),
arc_type: StdArc::type_name().as_str().to_string(),
version: 2,
flags: 0xffff_ffff,
properties: K_EXPANDED,
start: 0,
num_states: 3,
num_arcs: 2,
}
}
#[test]
fn a_header_and_its_symbols_round_trip() {
let isymbols = symbols("input", "a");
let osymbols = symbols("output", "x");
let mut bytes = Vec::new();
let written = write_fst_header(
&mut bytes,
&FstWriteOptions::default(),
&header(),
Some(&isymbols),
Some(&osymbols),
)
.unwrap();
assert_eq!(written.flags, flags::HAS_ISYMBOLS | flags::HAS_OSYMBOLS);
bytes.extend_from_slice(b"the states");
let mut reader = Cursor::new(bytes);
let read =
read_fst_header::<StdArc, _>(&mut reader, &FstReadOptions::default(), "vector", 2)
.unwrap();
assert_eq!(read.header, written);
assert_eq!(read.isymbols.unwrap().find_symbol(1), Some("a"));
assert_eq!(read.osymbols.unwrap().find_symbol(1), Some("x"));
let mut rest = Vec::new();
reader.read_to_end(&mut rest).unwrap();
assert_eq!(rest, b"the states");
}
#[test]
fn unwanted_symbols_are_still_read_past() {
let mut bytes = Vec::new();
write_fst_header(
&mut bytes,
&FstWriteOptions::default(),
&header(),
Some(&symbols("input", "a")),
Some(&symbols("output", "x")),
)
.unwrap();
bytes.extend_from_slice(b"the states");
let mut reader = Cursor::new(bytes);
let read = read_fst_header::<StdArc, _>(
&mut reader,
&FstReadOptions::default().read_symbols(false),
"vector",
2,
)
.unwrap();
assert!(read.isymbols.is_none() && read.osymbols.is_none());
let mut rest = Vec::new();
reader.read_to_end(&mut rest).unwrap();
assert_eq!(rest, b"the states");
}
#[test]
fn supplied_symbols_replace_the_files_own() {
let mut bytes = Vec::new();
write_fst_header(
&mut bytes,
&FstWriteOptions::default(),
&header(),
Some(&symbols("input", "a")),
None,
)
.unwrap();
bytes.extend_from_slice(b"the states");
let opts = FstReadOptions {
isymbols: Some(AtomicRc::new(symbols("supplied", "z"))),
..Default::default()
};
let mut reader = Cursor::new(bytes);
let read = read_fst_header::<StdArc, _>(&mut reader, &opts, "vector", 2).unwrap();
assert_eq!(read.isymbols.unwrap().find_symbol(1), Some("z"));
let mut rest = Vec::new();
reader.read_to_end(&mut rest).unwrap();
assert_eq!(rest, b"the states");
}
#[test]
fn a_header_supplied_by_the_caller_is_not_read_from_the_stream() {
let mut bytes = Vec::new();
let written = write_fst_header(
&mut bytes,
&FstWriteOptions::default(),
&header(),
Some(&symbols("input", "a")),
None,
)
.unwrap();
let header_len = bytes.len();
bytes.extend_from_slice(b"the states");
let symbols_at = {
let mut probe = Vec::new();
written.write(&mut probe).unwrap();
probe.len()
};
let mut reader = Cursor::new(bytes[symbols_at..].to_vec());
let opts = FstReadOptions::default().with_header(written.clone());
let read = read_fst_header::<StdArc, _>(&mut reader, &opts, "vector", 2).unwrap();
assert_eq!(read.header, written);
assert_eq!(read.isymbols.unwrap().find_symbol(1), Some("a"));
let mut rest = Vec::new();
reader.read_to_end(&mut rest).unwrap();
assert_eq!(rest, b"the states");
assert!(header_len > symbols_at);
}
#[test]
fn a_mismatched_type_version_or_arc_is_refused() {
let mut bytes = Vec::new();
write_fst_header(
&mut bytes,
&FstWriteOptions::default(),
&header(),
None,
None,
)
.unwrap();
let opts = FstReadOptions::default();
let read = |fst_type: &str, min_version| {
read_fst_header::<StdArc, _>(
&mut Cursor::new(bytes.clone()),
&opts,
fst_type,
min_version,
)
};
assert!(read("vector", 2).is_ok());
assert!(read("const", 2).is_err(), "wrong FST type accepted");
assert!(read("vector", 3).is_err(), "obsolete version accepted");
let mut other = header();
other.arc_type = "log".to_string();
let mut bytes = Vec::new();
write_fst_header(&mut bytes, &FstWriteOptions::default(), &other, None, None).unwrap();
assert!(
read_fst_header::<StdArc, _>(&mut Cursor::new(bytes), &opts, "vector", 2).is_err(),
"wrong arc type accepted"
);
}
#[test]
fn writing_can_be_told_to_skip_the_header_or_a_table() {
let opts = FstWriteOptions {
write_header: false,
write_osymbols: false,
..Default::default()
};
let mut bytes = Vec::new();
let written = write_fst_header(
&mut bytes,
&opts,
&header(),
Some(&symbols("input", "a")),
Some(&symbols("output", "x")),
)
.unwrap();
assert_eq!(written.flags, flags::HAS_ISYMBOLS);
let mut table_only = Vec::new();
symbols("input", "a").write(&mut table_only).unwrap();
assert_eq!(bytes, table_only);
}
#[test]
fn alignment_is_recorded_in_the_flags() {
let opts = FstWriteOptions {
align: true,
..Default::default()
};
let mut bytes = Vec::new();
let written = write_fst_header(&mut bytes, &opts, &header(), None, None).unwrap();
assert_eq!(written.flags, flags::IS_ALIGNED);
}
#[test]
fn read_modes_round_trip_through_their_names() {
for mode in [FileReadMode::Read, FileReadMode::Map] {
assert_eq!(FileReadMode::from_name(mode.as_str()), Some(mode));
}
assert_eq!(FileReadMode::from_name("READ"), None);
assert_eq!(FileReadMode::from_name("mmap"), None);
}
#[test]
fn options_and_headers_print_the_way_openfst_prints_them() {
assert_eq!(
FstReadOptions::new("x.fst").to_string(),
"source: \"x.fst\" mode: \"READ\" read_isymbols: \"true\" \
read_osymbols: \"true\" header: \"null\" isymbols: \"null\" \
osymbols: \"null\" verify: \"true\""
);
let opts = FstReadOptions {
isymbols: Some(AtomicRc::new(symbols("input", "a"))),
mode: FileReadMode::Map,
read_osymbols: false,
verify: false,
..FstReadOptions::new("y.fst")
};
assert_eq!(
opts.to_string(),
"source: \"y.fst\" mode: \"MAP\" read_isymbols: \"true\" \
read_osymbols: \"false\" header: \"null\" isymbols: \"set\" \
osymbols: \"null\" verify: \"false\""
);
let mut header = header();
header.flags = flags::HAS_ISYMBOLS;
assert_eq!(
header.to_string(),
"fsttype: \"vector\" arctype: \"standard\" version: \"2\" \
flags: \"1\" properties: \"1\" start: \"0\" numstates: \"3\" \
numarcs: \"2\""
);
}
#[test]
fn expanded_fsts_agree_with_themselves_about_their_size() {
use crate::fsts::const_fst::ConstFst;
use crate::fsts::vector_fst::VectorFst;
fn check<A: Arc, F: ExpandedFst<A>>(fst: &F, expected: usize) {
assert_eq!(fst.num_states(), expected);
assert_eq!(fst.num_states_if_known(), Some(expected));
assert_eq!(fst.count_states(), expected);
}
let mut vector = VectorFst::<StdArc>::new();
for _ in 0..4 {
vector.add_state();
}
vector.set_start(0);
vector.add_arc(0, StdArc::new(1, 1, TropicalWeight::one(), 1));
vector.add_arc(0, StdArc::new(2, 2, TropicalWeight::one(), 2));
vector.add_arc(1, StdArc::new(3, 3, TropicalWeight::one(), 3));
check(&vector, 4);
let constant = ConstFst::<StdArc, u32>::from_fst(&vector).unwrap();
check(&constant, 4);
assert_eq!(vector.count_arcs(), 3);
assert_eq!(constant.count_arcs(), 3);
}
#[test]
fn an_fst_that_does_not_know_its_size_is_counted() {
struct Unsized(usize);
impl Fst<StdArc> for Unsized {
type StateIter<'a> = std::ops::Range<i32>;
type ArcIter<'a> = std::iter::Empty<StdArc>;
fn start(&self) -> Option<i32> {
Some(0)
}
fn final_weight(&self, _state: i32) -> TropicalWeight {
TropicalWeight::zero()
}
fn num_arcs(&self, _state: i32) -> usize {
0
}
fn num_input_epsilons(&self, _state: i32) -> usize {
0
}
fn num_output_epsilons(&self, _state: i32) -> usize {
0
}
fn num_states_if_known(&self) -> Option<usize> {
None
}
fn properties(&self, _mask: u64, _test: bool) -> u64 {
0
}
fn fst_type(&self) -> &str {
"unsized"
}
fn input_symbols(&self) -> Option<AtomicRc<SymbolTable>> {
None
}
fn output_symbols(&self) -> Option<AtomicRc<SymbolTable>> {
None
}
fn states<'a>(&'a self) -> Self::StateIter<'a> {
0..self.0 as i32
}
fn arcs<'a>(&'a self, _state: i32) -> Self::ArcIter<'a> {
std::iter::empty()
}
}
let fst = Unsized(7);
assert_eq!(fst.num_states_if_known(), None);
assert_eq!(fst.count_states(), 7);
assert_eq!(count_states(&fst), 7);
assert_eq!(count_states_slice(&[&fst, &fst]), 14);
}
}
#[cfg(test)]
mod arc_range_tests {
use super::*;
use crate::arc::StdArc;
use crate::fsts::vector_fst::StdVectorFst;
use crate::weight::Weight;
use crate::weights::float_weight::TropicalWeight;
fn fst() -> StdVectorFst {
let mut fst = StdVectorFst::new();
for _ in 0..2 {
fst.add_state();
}
fst.set_start(0);
fst.set_final(1, TropicalWeight::one());
for label in [7, 42, 9] {
fst.add_arc(0, StdArc::new(label, label, TropicalWeight::one(), 1));
}
fst
}
#[test]
fn arcs_are_iterable_and_searchable_without_a_wrapper() {
let fst = fst();
let mut labels = Vec::new();
for arc in fst.arcs(0) {
labels.push(arc.olabel());
}
assert_eq!(labels, vec![7, 42, 9]);
let found = fst.arcs(0).find(|arc| arc.olabel() == 42);
assert!(found.is_some());
assert_eq!(fst.arcs(0).filter(|arc| arc.olabel() > 8).count(), 2);
}
#[test]
fn contiguous_arcs_are_a_slice() {
let fst = fst();
let arcs = fst.arcs_slice(0);
assert_eq!(arcs.len(), 3);
assert_eq!(arcs[1].olabel(), 42);
assert_eq!(arcs.last().unwrap().olabel(), 9);
assert!(arcs.binary_search_by_key(&7, |arc| arc.olabel()).is_ok());
}
}