use crate::{
ExplicitWithSignature, FeatureStructure, Irtg, TagStringValue, TreeValue,
codecs::TulipacInputCodec,
};
use std::{
any::{Any, TypeId},
collections::HashMap,
error::Error,
fmt, fs,
io::{Cursor, Read},
path::Path,
};
#[derive(Debug)]
pub enum InputCodecError {
Io(std::io::Error),
Utf8(std::string::FromUtf8Error),
Codec(Box<dyn Error + Send + Sync>),
MissingExtension,
UnknownCodec(String),
}
impl InputCodecError {
pub fn codec(error: impl Error + Send + Sync + 'static) -> Self {
Self::Codec(Box::new(error))
}
}
impl fmt::Display for InputCodecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => write!(f, "failed to read codec input: {error}"),
Self::Utf8(error) => write!(f, "codec input is not valid UTF-8: {error}"),
Self::Codec(error) => error.fmt(f),
Self::MissingExtension => f.write_str("input path has no filename extension"),
Self::UnknownCodec(format) => write!(f, "no input codec registered for {format:?}"),
}
}
}
impl Error for InputCodecError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::Utf8(error) => Some(error),
Self::Codec(error) => Some(&**error),
Self::MissingExtension | Self::UnknownCodec(_) => None,
}
}
}
impl From<std::io::Error> for InputCodecError {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
impl From<std::string::FromUtf8Error> for InputCodecError {
fn from(error: std::string::FromUtf8Error) -> Self {
Self::Utf8(error)
}
}
pub trait InputCodec<T>: Send + Sync {
fn metadata(&self) -> &'static CodecMetadata;
fn read(&self, reader: &mut dyn Read) -> Result<T, InputCodecError>;
fn read_path(&self, path: &Path) -> Result<T, InputCodecError> {
let mut file = fs::File::open(path)?;
self.read(&mut file)
}
fn decode(&self, input: &str) -> Result<T, InputCodecError> {
self.read(&mut Cursor::new(input.as_bytes()))
}
fn read_bytes(&self, input: &[u8]) -> Result<T, InputCodecError> {
self.read(&mut Cursor::new(input))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct IrtgInputCodec;
impl InputCodec<crate::Irtg> for IrtgInputCodec {
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "irtg",
description: "IRTG grammar",
extension: Some("irtg"),
};
&METADATA
}
fn read(&self, reader: &mut dyn Read) -> Result<crate::Irtg, InputCodecError> {
crate::parse_irtg(reader).map_err(InputCodecError::codec)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AltoTreeAutomatonInputCodec;
impl InputCodec<ExplicitWithSignature> for AltoTreeAutomatonInputCodec {
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "auto",
description: "Tree automaton",
extension: Some("auto"),
};
&METADATA
}
fn read(&self, reader: &mut dyn Read) -> Result<ExplicitWithSignature, InputCodecError> {
let input = read_utf8(reader)?;
crate::parse_alto(&input).map_err(InputCodecError::codec)
}
}
fn read_utf8(reader: &mut dyn Read) -> Result<String, InputCodecError> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes)?;
String::from_utf8(bytes).map_err(InputCodecError::Utf8)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CodecMetadata {
pub name: &'static str,
pub description: &'static str,
pub extension: Option<&'static str>,
}
pub type RegisteredInputCodec<T> = dyn InputCodec<T> + Send + Sync;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InputCodecRegistryError {
DuplicateName(String),
DuplicateExtension(String),
}
impl fmt::Display for InputCodecRegistryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateName(name) => {
write!(f, "duplicate input codec name {name:?} for one result type")
}
Self::DuplicateExtension(extension) => write!(
f,
"duplicate input codec extension {extension:?} for one result type"
),
}
}
}
impl Error for InputCodecRegistryError {}
#[derive(Default)]
pub struct InputCodecRegistry {
codecs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}
impl InputCodecRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn standard() -> Self {
let mut registry = Self::new();
registry
.register::<Irtg, _>(IrtgInputCodec)
.expect("built-in input codec metadata is unique");
registry
.register::<Irtg, _>(TulipacInputCodec)
.expect("built-in input codec metadata is unique");
registry
.register::<ExplicitWithSignature, _>(AltoTreeAutomatonInputCodec)
.expect("built-in input codec metadata is unique");
registry
}
pub fn register<T: 'static, C>(&mut self, codec: C) -> Result<(), InputCodecRegistryError>
where
C: InputCodec<T> + 'static,
{
let codecs = self
.codecs
.entry(TypeId::of::<T>())
.or_insert_with(|| Box::new(Vec::<Box<RegisteredInputCodec<T>>>::new()))
.downcast_mut::<Vec<Box<RegisteredInputCodec<T>>>>()
.expect("a TypeId uniquely identifies its codec vector");
let metadata = codec.metadata();
let name = normalize_name(metadata.name);
if codecs
.iter()
.any(|registered| normalize_name(registered.metadata().name) == name)
{
return Err(InputCodecRegistryError::DuplicateName(name));
}
if let Some(extension) = metadata.extension.map(normalize_extension)
&& codecs.iter().any(|registered| {
registered
.metadata()
.extension
.map(normalize_extension)
.as_deref()
== Some(extension.as_str())
})
{
return Err(InputCodecRegistryError::DuplicateExtension(extension));
}
codecs.push(Box::new(codec));
Ok(())
}
pub fn codecs_for<T: 'static>(&self) -> &[Box<RegisteredInputCodec<T>>] {
self.codecs
.get(&TypeId::of::<T>())
.and_then(|codecs| codecs.downcast_ref::<Vec<Box<RegisteredInputCodec<T>>>>())
.map_or(&[], Vec::as_slice)
}
pub fn codec_for_name<T: 'static>(
&self,
name: &str,
) -> Result<&RegisteredInputCodec<T>, InputCodecError> {
let normalized = normalize_name(name);
self.codecs_for::<T>()
.iter()
.find(|codec| normalize_name(codec.metadata().name) == normalized)
.map(Box::as_ref)
.ok_or_else(|| InputCodecError::UnknownCodec(name.to_owned()))
}
pub fn codec_for_extension<T: 'static>(
&self,
extension: &str,
) -> Result<&RegisteredInputCodec<T>, InputCodecError> {
let normalized = normalize_extension(extension);
self.codecs_for::<T>()
.iter()
.find(|codec| {
codec
.metadata()
.extension
.map(normalize_extension)
.as_deref()
== Some(normalized.as_str())
})
.map(Box::as_ref)
.ok_or_else(|| InputCodecError::UnknownCodec(extension.to_owned()))
}
pub fn codec_for_path<T: 'static>(
&self,
path: &Path,
) -> Result<&RegisteredInputCodec<T>, InputCodecError> {
let extension = path
.extension()
.and_then(|extension| extension.to_str())
.ok_or(InputCodecError::MissingExtension)?;
self.codec_for_extension(extension)
}
}
fn normalize_name(name: &str) -> String {
name.trim().to_ascii_lowercase()
}
fn normalize_extension(extension: &str) -> String {
extension
.trim()
.trim_start_matches('.')
.to_ascii_lowercase()
}
pub trait OutputCodec<V: ?Sized> {
type Output;
fn metadata(&self) -> &'static CodecMetadata;
fn encode(&self, value: &V) -> Self::Output;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DisplayCodec;
impl<V: fmt::Display + ?Sized> OutputCodec<V> for DisplayCodec {
type Output = String;
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "display",
description: "Default string representation",
extension: Some("txt"),
};
&METADATA
}
fn encode(&self, value: &V) -> Self::Output {
value.to_string()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SpaceJoinCodec;
impl OutputCodec<Vec<String>> for SpaceJoinCodec {
type Output = String;
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "string",
description: "Space-separated string",
extension: Some("txt"),
};
&METADATA
}
#[allow(clippy::ptr_arg)] fn encode(&self, value: &Vec<String>) -> Self::Output {
value.join(" ")
}
}
#[derive(Debug)]
pub enum VisualRepresentation {
Text(String),
Tree(TreeValue),
FeatureStructure(FeatureStructure),
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TextVisualizationCodec<C> {
codec: C,
}
impl<C> TextVisualizationCodec<C> {
pub fn new(codec: C) -> Self {
Self { codec }
}
}
impl<V: ?Sized, C> OutputCodec<V> for TextVisualizationCodec<C>
where
C: OutputCodec<V, Output = String>,
{
type Output = VisualRepresentation;
fn metadata(&self) -> &'static CodecMetadata {
self.codec.metadata()
}
fn encode(&self, value: &V) -> Self::Output {
VisualRepresentation::Text(self.codec.encode(value))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TreeVisualizationCodec;
impl OutputCodec<TreeValue> for TreeVisualizationCodec {
type Output = VisualRepresentation;
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "tree-visualization",
description: "Tree visualization",
extension: None,
};
&METADATA
}
fn encode(&self, value: &TreeValue) -> Self::Output {
VisualRepresentation::Tree(value.clone())
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FeatureStructureVisualizationCodec;
impl OutputCodec<FeatureStructure> for FeatureStructureVisualizationCodec {
type Output = VisualRepresentation;
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "feature-structure-visualization",
description: "Feature-structure visualization",
extension: None,
};
&METADATA
}
fn encode(&self, value: &FeatureStructure) -> Self::Output {
VisualRepresentation::FeatureStructure(value.clone())
}
}
pub type TextOutputCodec<V> = dyn OutputCodec<V, Output = String> + Send + Sync;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OutputCodecError {
UnknownCodec(String),
}
impl fmt::Display for OutputCodecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownCodec(name) => write!(f, "no output codec registered with name {name:?}"),
}
}
}
impl Error for OutputCodecError {}
#[derive(Default)]
pub struct OutputCodecRegistry {
codecs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}
impl OutputCodecRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn standard() -> Self {
let mut registry = Self::new();
registry.register::<Vec<String>, _>(SpaceJoinCodec);
registry.register::<TagStringValue<String>, _>(DisplayCodec);
registry.register::<TreeValue, _>(DisplayCodec);
registry.register::<FeatureStructure, _>(DisplayCodec);
registry
}
pub fn register<V: 'static, C>(&mut self, codec: C)
where
C: OutputCodec<V, Output = String> + Send + Sync + 'static,
{
self.codecs
.entry(TypeId::of::<V>())
.or_insert_with(|| Box::new(Vec::<Box<TextOutputCodec<V>>>::new()))
.downcast_mut::<Vec<Box<TextOutputCodec<V>>>>()
.expect("a TypeId uniquely identifies its codec vector")
.push(Box::new(codec));
}
pub fn codecs_for<V: 'static>(&self) -> &[Box<TextOutputCodec<V>>] {
self.codecs
.get(&TypeId::of::<V>())
.and_then(|codecs| codecs.downcast_ref::<Vec<Box<TextOutputCodec<V>>>>())
.map_or(&[], Vec::as_slice)
}
pub fn codec_for_name<V: 'static>(
&self,
name: &str,
) -> Result<&TextOutputCodec<V>, OutputCodecError> {
let normalized = normalize_name(name);
self.codecs_for::<V>()
.iter()
.find(|codec| normalize_name(codec.metadata().name) == normalized)
.map(Box::as_ref)
.ok_or_else(|| OutputCodecError::UnknownCodec(name.to_owned()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
#[derive(Clone)]
struct CountingInputCodec {
calls: Arc<AtomicUsize>,
metadata: &'static CodecMetadata,
}
impl InputCodec<u32> for CountingInputCodec {
fn metadata(&self) -> &'static CodecMetadata {
self.metadata
}
fn read(&self, reader: &mut dyn Read) -> Result<u32, InputCodecError> {
self.calls.fetch_add(1, Ordering::Relaxed);
let input = read_utf8(reader)?;
input.trim().parse().map_err(InputCodecError::codec)
}
}
static COUNTING_METADATA: CodecMetadata = CodecMetadata {
name: "count",
description: "Count",
extension: Some("count"),
};
static DUPLICATE_NAME_METADATA: CodecMetadata = CodecMetadata {
name: "COUNT",
description: "Duplicate count",
extension: Some("other"),
};
static DUPLICATE_EXTENSION_METADATA: CodecMetadata = CodecMetadata {
name: "other",
description: "Duplicate extension",
extension: Some(".COUNT"),
};
#[test]
fn standard_input_registry_is_partitioned_by_exact_result_type() {
let registry = InputCodecRegistry::standard();
let irtg_extensions = registry
.codecs_for::<Irtg>()
.iter()
.filter_map(|codec| codec.metadata().extension)
.collect::<Vec<_>>();
assert_eq!(irtg_extensions, vec!["irtg", "tag"]);
let auto_extensions = registry
.codecs_for::<ExplicitWithSignature>()
.iter()
.filter_map(|codec| codec.metadata().extension)
.collect::<Vec<_>>();
assert_eq!(auto_extensions, vec!["auto"]);
assert!(registry.codecs_for::<u32>().is_empty());
}
#[test]
fn input_registry_lookup_is_normalized_and_lazy() {
let calls = Arc::new(AtomicUsize::new(0));
let mut registry = InputCodecRegistry::new();
registry
.register::<u32, _>(CountingInputCodec {
calls: calls.clone(),
metadata: &COUNTING_METADATA,
})
.unwrap();
assert_eq!(
registry
.codec_for_name::<u32>("COUNT")
.unwrap()
.metadata()
.name,
"count"
);
assert_eq!(
registry
.codec_for_extension::<u32>(".CoUnT")
.unwrap()
.metadata()
.extension,
Some("count")
);
assert_eq!(
registry
.codec_for_path::<u32>(Path::new("value.COUNT"))
.unwrap()
.metadata()
.name,
"count"
);
assert_eq!(calls.load(Ordering::Relaxed), 0);
assert_eq!(
registry
.codec_for_extension::<u32>("count")
.unwrap()
.decode("17")
.unwrap(),
17
);
assert_eq!(calls.load(Ordering::Relaxed), 1);
assert!(matches!(
registry.codec_for_path::<u32>(Path::new("no-extension")),
Err(InputCodecError::MissingExtension)
));
assert!(matches!(
registry.codec_for_extension::<u32>("unknown"),
Err(InputCodecError::UnknownCodec(_))
));
}
#[test]
fn input_registry_rejects_duplicate_names_and_extensions_per_type() {
let calls = Arc::new(AtomicUsize::new(0));
let mut registry = InputCodecRegistry::new();
registry
.register::<u32, _>(CountingInputCodec {
calls: calls.clone(),
metadata: &COUNTING_METADATA,
})
.unwrap();
assert_eq!(
registry.register::<u32, _>(CountingInputCodec {
calls: calls.clone(),
metadata: &DUPLICATE_NAME_METADATA,
}),
Err(InputCodecRegistryError::DuplicateName("count".to_owned()))
);
assert_eq!(
registry.register::<u32, _>(CountingInputCodec {
calls,
metadata: &DUPLICATE_EXTENSION_METADATA,
}),
Err(InputCodecRegistryError::DuplicateExtension(
"count".to_owned()
))
);
assert!(
registry.register::<u64, _>(U64InputCodec).is_ok(),
"the same metadata is allowed for another result type"
);
}
struct U64InputCodec;
impl InputCodec<u64> for U64InputCodec {
fn metadata(&self) -> &'static CodecMetadata {
&COUNTING_METADATA
}
fn read(&self, reader: &mut dyn Read) -> Result<u64, InputCodecError> {
read_utf8(reader)?
.trim()
.parse()
.map_err(InputCodecError::codec)
}
}
#[test]
fn built_in_input_codecs_decode_expected_types() {
let irtg = IrtgInputCodec
.decode(
"interpretation string: de.up.ling.irtg.algebra.StringAlgebra\n\
S! -> word\n[string] hello\n",
)
.unwrap();
assert!(irtg.interpretation_ref("string").is_some());
let auto = AltoTreeAutomatonInputCodec
.decode("S! -> f(A)\nA -> a")
.unwrap();
assert_eq!(auto.states.resolve(crate::StateId(0)), "S");
assert!(auto.signature.get("f").is_some());
assert_eq!(auto.automaton.rules().count(), 2);
}
#[test]
fn display_codec_uses_display() {
assert_eq!(DisplayCodec.encode(&2u8), "2");
assert_eq!(DisplayCodec.encode("hi"), "hi");
assert_eq!(
<DisplayCodec as OutputCodec<u8>>::metadata(&DisplayCodec).extension,
Some("txt")
);
}
#[test]
fn space_join_codec_joins_words() {
assert_eq!(
SpaceJoinCodec.encode(&vec!["the".to_owned(), "woman".to_owned()]),
"the woman"
);
assert_eq!(SpaceJoinCodec.encode(&vec![]), "");
assert_eq!(SpaceJoinCodec.encode(&vec!["the".to_owned()]), "the");
}
#[test]
fn text_visualization_wraps_text() {
let codec = TextVisualizationCodec::new(SpaceJoinCodec);
let value = vec!["hello".to_owned(), "world".to_owned()];
assert!(matches!(
codec.encode(&value),
VisualRepresentation::Text(text) if text == "hello world"
));
}
#[test]
fn registry_uses_exact_types_and_does_not_encode_during_lookup() {
struct CountingCodec(Arc<AtomicUsize>);
impl OutputCodec<u32> for CountingCodec {
type Output = String;
fn metadata(&self) -> &'static CodecMetadata {
static METADATA: CodecMetadata = CodecMetadata {
name: "counting",
description: "Counting codec",
extension: Some("count"),
};
&METADATA
}
fn encode(&self, value: &u32) -> Self::Output {
self.0.fetch_add(1, Ordering::Relaxed);
value.to_string()
}
}
let calls = Arc::new(AtomicUsize::new(0));
let mut registry = OutputCodecRegistry::new();
registry.register::<u32, _>(CountingCodec(calls.clone()));
let codecs = registry.codecs_for::<u32>();
assert_eq!(codecs.len(), 1);
assert_eq!(codecs[0].metadata().extension, Some("count"));
assert!(registry.codecs_for::<u64>().is_empty());
assert_eq!(calls.load(Ordering::Relaxed), 0);
assert_eq!(registry.codec_for_name::<u32>("COUNTING").unwrap().encode(&7), "7");
assert_eq!(calls.load(Ordering::Relaxed), 1);
assert!(matches!(
registry.codec_for_name::<u64>("counting"),
Err(OutputCodecError::UnknownCodec(_))
));
}
}