use std::io::{Read, Write};
use std::sync::Arc as StdArc;
use crate::error::OpenFstError;
use crate::fst::{FstReadOptions, FstWriteOptions};
use crate::utils::io::{read_scalar, write_scalar};
#[cfg(feature = "fst-types")]
pub use impl_::AddOnImpl;
pub const ADD_ON_MAGIC_NUMBER: i32 = 446_681_434;
pub trait AddOn: Sized {
fn read<R: Read>(reader: &mut R, opts: &FstReadOptions) -> Result<Self, OpenFstError>;
fn write<W: Write>(&self, writer: &mut W, opts: &FstWriteOptions) -> Result<(), OpenFstError>;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NullAddOn;
impl AddOn for NullAddOn {
fn read<R: Read>(_reader: &mut R, _opts: &FstReadOptions) -> Result<Self, OpenFstError> {
Ok(Self)
}
fn write<W: Write>(
&self,
_writer: &mut W,
_opts: &FstWriteOptions,
) -> Result<(), OpenFstError> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AddOnPair<A1, A2> {
first: Option<StdArc<A1>>,
second: Option<StdArc<A2>>,
}
impl<A1, A2> AddOnPair<A1, A2> {
pub fn new(first: Option<StdArc<A1>>, second: Option<StdArc<A2>>) -> Self {
Self { first, second }
}
pub fn first(&self) -> Option<&A1> {
self.first.as_deref()
}
pub fn second(&self) -> Option<&A2> {
self.second.as_deref()
}
pub fn shared_first(&self) -> Option<StdArc<A1>> {
self.first.clone()
}
pub fn shared_second(&self) -> Option<StdArc<A2>> {
self.second.clone()
}
}
impl<A1: AddOn, A2: AddOn> AddOn for AddOnPair<A1, A2> {
fn read<R: Read>(reader: &mut R, opts: &FstReadOptions) -> Result<Self, OpenFstError> {
let has_first: bool = read_scalar(reader)?;
let first = if has_first {
Some(StdArc::new(A1::read(reader, opts)?))
} else {
None
};
let has_second: bool = read_scalar(reader)?;
let second = if has_second {
Some(StdArc::new(A2::read(reader, opts)?))
} else {
None
};
Ok(Self::new(first, second))
}
fn write<W: Write>(&self, writer: &mut W, opts: &FstWriteOptions) -> Result<(), OpenFstError> {
write_scalar(writer, self.first.is_some())?;
if let Some(first) = &self.first {
first.write(writer, opts)?;
}
write_scalar(writer, self.second.is_some())?;
if let Some(second) = &self.second {
second.write(writer, opts)?;
}
Ok(())
}
}
#[cfg(feature = "fst-types")]
mod impl_ {
use std::io::{Read, Seek, Write};
use std::sync::Arc as StdArc;
use super::{ADD_ON_MAGIC_NUMBER, AddOn};
use crate::arc::ArcStateId;
use crate::error::OpenFstError;
use crate::fst::{
ExpandedFst, Fst, FstReadOptions, FstWriteOptions, read_fst_header, write_fst_header,
};
use crate::fst_header::FstHeader;
use crate::fst_type::FstType;
use crate::fsts::any_fst::AnyFst;
use crate::properties::K_FST_PROPERTIES;
use crate::utils::io::{FstScalar, read_scalar, write_scalar};
use crate::weight::WeightIo;
pub struct AddOnImpl<'f, A: crate::arc::Arc + 'static, T>
where
A::Weight: Copy,
{
fst: AnyFst<'f, A>,
fst_type: FstType,
add_on: Option<StdArc<T>>,
}
const FILE_VERSION: i32 = 1;
const MIN_FILE_VERSION: i32 = 1;
impl<'f, A: crate::arc::Arc + 'static, T> AddOnImpl<'f, A, T>
where
A::Weight: Copy,
{
pub fn new(fst: AnyFst<'f, A>, fst_type: FstType, add_on: Option<StdArc<T>>) -> Self {
Self {
fst,
fst_type,
add_on,
}
}
pub fn fst(&self) -> &AnyFst<'f, A> {
&self.fst
}
pub fn fst_type(&self) -> FstType {
self.fst_type.clone()
}
pub fn add_on(&self) -> Option<&T> {
self.add_on.as_deref()
}
pub fn shared_add_on(&self) -> Option<StdArc<T>> {
self.add_on.clone()
}
pub fn set_add_on(&mut self, add_on: Option<StdArc<T>>) {
self.add_on = add_on;
}
}
impl<'f, A, T> AddOnImpl<'f, A, T>
where
A: crate::arc::Arc + 'static,
A::Label: FstScalar,
A::StateId: FstScalar,
A::Weight: Copy + WeightIo,
T: AddOn,
{
pub fn write<W: Write>(
&self,
writer: &mut W,
opts: &FstWriteOptions,
) -> Result<(), OpenFstError> {
let header = FstHeader {
fst_type: self.fst_type.as_str().to_string(),
arc_type: A::type_name().as_str().to_string(),
version: FILE_VERSION,
flags: 0,
properties: self.fst.properties(K_FST_PROPERTIES, false),
start: self.fst.start().map_or(-1, |s| s.as_usize() as i64),
num_states: self.fst.num_states() as i64,
num_arcs: -1,
};
let header_opts = FstWriteOptions {
write_isymbols: false,
write_osymbols: false,
..opts.clone()
};
write_fst_header(writer, &header_opts, &header, None, None)?;
write_scalar(writer, ADD_ON_MAGIC_NUMBER)?;
let contained = FstWriteOptions {
write_header: true,
..opts.clone()
};
self.fst.write(writer, &contained)?;
write_scalar(writer, self.add_on.is_some())?;
if let Some(add_on) = &self.add_on {
add_on.write(writer, opts)?;
}
Ok(())
}
}
impl<A, T> AddOnImpl<'static, A, T>
where
A: crate::arc::Arc + 'static,
A::Label: FstScalar,
A::StateId: FstScalar,
A::Weight: Copy + WeightIo,
T: AddOn,
{
pub fn read<R: Read + Seek>(
reader: &mut R,
opts: &FstReadOptions,
fst_type: FstType,
) -> Result<Self, OpenFstError> {
read_fst_header::<A, _>(reader, opts, fst_type.as_str(), MIN_FILE_VERSION)?;
let magic: i32 = read_scalar(reader)?;
if magic != ADD_ON_MAGIC_NUMBER {
return Err(OpenFstError::InvalidFstHeader(format!(
"{}: bad add-on header",
opts.source
)));
}
let contained = FstReadOptions {
header: None,
..opts.clone()
};
let fst = AnyFst::read(reader, &contained)?;
let has_add_on: bool = read_scalar(reader)?;
let add_on = if has_add_on {
Some(StdArc::new(T::read(reader, &contained)?))
} else {
None
};
Ok(Self::new(fst, fst_type, add_on))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[derive(Debug, Clone, PartialEq, Eq)]
struct Counter(i32);
impl AddOn for Counter {
fn read<R: Read>(reader: &mut R, _opts: &FstReadOptions) -> Result<Self, OpenFstError> {
Ok(Self(read_scalar(reader)?))
}
fn write<W: Write>(
&self,
writer: &mut W,
_opts: &FstWriteOptions,
) -> Result<(), OpenFstError> {
write_scalar(writer, self.0).map_err(Into::into)
}
}
fn round_trip<T: AddOn>(value: &T) -> T {
let mut bytes = Vec::new();
value
.write(&mut bytes, &FstWriteOptions::default())
.unwrap();
T::read(&mut Cursor::new(bytes), &FstReadOptions::default()).unwrap()
}
#[test]
fn the_magic_number_matches_openfst() {
assert_eq!(ADD_ON_MAGIC_NUMBER, 446_681_434);
}
#[test]
fn the_null_add_on_writes_nothing() {
let mut bytes = Vec::new();
NullAddOn
.write(&mut bytes, &FstWriteOptions::default())
.unwrap();
assert!(bytes.is_empty());
assert_eq!(round_trip(&NullAddOn), NullAddOn);
}
#[test]
fn a_pair_round_trips_with_both_halves() {
let pair = AddOnPair::new(
Some(StdArc::new(Counter(7))),
Some(StdArc::new(Counter(-3))),
);
let read = round_trip(&pair);
assert_eq!(read.first(), Some(&Counter(7)));
assert_eq!(read.second(), Some(&Counter(-3)));
}
#[test]
fn an_absent_half_costs_one_byte() {
let pair: AddOnPair<Counter, Counter> = AddOnPair::new(None, Some(StdArc::new(Counter(1))));
let mut bytes = Vec::new();
pair.write(&mut bytes, &FstWriteOptions::default()).unwrap();
assert_eq!(bytes.len(), 1 + 1 + 4);
assert_eq!(bytes[0], 0);
assert_eq!(bytes[1], 1);
let read = round_trip(&pair);
assert!(read.first().is_none());
assert_eq!(read.second(), Some(&Counter(1)));
}
#[test]
fn a_pair_with_neither_half_is_two_bytes() {
let pair: AddOnPair<Counter, Counter> = AddOnPair::new(None, None);
let mut bytes = Vec::new();
pair.write(&mut bytes, &FstWriteOptions::default()).unwrap();
assert_eq!(bytes, vec![0, 0]);
let read = round_trip(&pair);
assert!(read.first().is_none() && read.second().is_none());
}
#[test]
fn pairs_nest() {
type Nested = AddOnPair<AddOnPair<Counter, Counter>, Counter>;
let inner = AddOnPair::new(Some(StdArc::new(Counter(1))), None);
let outer: Nested = AddOnPair::new(Some(StdArc::new(inner)), Some(StdArc::new(Counter(2))));
let read = round_trip(&outer);
assert_eq!(read.first().unwrap().first(), Some(&Counter(1)));
assert!(read.first().unwrap().second().is_none());
assert_eq!(read.second(), Some(&Counter(2)));
}
}