1use crate::{
20 ExplicitWithSignature, FeatureStructure, Irtg, TagStringValue, TreeValue,
21 codecs::TulipacInputCodec,
22};
23use std::{
24 any::{Any, TypeId},
25 collections::HashMap,
26 error::Error,
27 fmt, fs,
28 io::{Cursor, Read},
29 path::Path,
30};
31
32#[derive(Debug)]
34pub enum InputCodecError {
35 Io(std::io::Error),
37 Utf8(std::string::FromUtf8Error),
39 Codec(Box<dyn Error + Send + Sync>),
41 MissingExtension,
43 UnknownCodec(String),
45}
46
47impl InputCodecError {
48 pub fn codec(error: impl Error + Send + Sync + 'static) -> Self {
50 Self::Codec(Box::new(error))
51 }
52}
53
54impl fmt::Display for InputCodecError {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::Io(error) => write!(f, "failed to read codec input: {error}"),
58 Self::Utf8(error) => write!(f, "codec input is not valid UTF-8: {error}"),
59 Self::Codec(error) => error.fmt(f),
60 Self::MissingExtension => f.write_str("input path has no filename extension"),
61 Self::UnknownCodec(format) => write!(f, "no input codec registered for {format:?}"),
62 }
63 }
64}
65
66impl Error for InputCodecError {
67 fn source(&self) -> Option<&(dyn Error + 'static)> {
68 match self {
69 Self::Io(error) => Some(error),
70 Self::Utf8(error) => Some(error),
71 Self::Codec(error) => Some(&**error),
72 Self::MissingExtension | Self::UnknownCodec(_) => None,
73 }
74 }
75}
76
77impl From<std::io::Error> for InputCodecError {
78 fn from(error: std::io::Error) -> Self {
79 Self::Io(error)
80 }
81}
82
83impl From<std::string::FromUtf8Error> for InputCodecError {
84 fn from(error: std::string::FromUtf8Error) -> Self {
85 Self::Utf8(error)
86 }
87}
88
89pub trait InputCodec<T>: Send + Sync {
91 fn metadata(&self) -> &'static CodecMetadata;
93
94 fn read(&self, reader: &mut dyn Read) -> Result<T, InputCodecError>;
96
97 fn read_path(&self, path: &Path) -> Result<T, InputCodecError> {
99 let mut file = fs::File::open(path)?;
100 self.read(&mut file)
101 }
102
103 fn decode(&self, input: &str) -> Result<T, InputCodecError> {
105 self.read(&mut Cursor::new(input.as_bytes()))
106 }
107
108 fn read_bytes(&self, input: &[u8]) -> Result<T, InputCodecError> {
110 self.read(&mut Cursor::new(input))
111 }
112}
113
114#[derive(Clone, Copy, Debug, Default)]
116pub struct IrtgInputCodec;
117
118impl InputCodec<crate::Irtg> for IrtgInputCodec {
119 fn metadata(&self) -> &'static CodecMetadata {
120 static METADATA: CodecMetadata = CodecMetadata {
121 name: "irtg",
122 description: "IRTG grammar",
123 extension: Some("irtg"),
124 };
125 &METADATA
126 }
127
128 fn read(&self, reader: &mut dyn Read) -> Result<crate::Irtg, InputCodecError> {
129 crate::parse_irtg(reader).map_err(InputCodecError::codec)
130 }
131}
132
133#[derive(Clone, Copy, Debug, Default)]
135pub struct AltoTreeAutomatonInputCodec;
136
137impl InputCodec<ExplicitWithSignature> for AltoTreeAutomatonInputCodec {
138 fn metadata(&self) -> &'static CodecMetadata {
139 static METADATA: CodecMetadata = CodecMetadata {
140 name: "auto",
141 description: "Tree automaton",
142 extension: Some("auto"),
143 };
144 &METADATA
145 }
146
147 fn read(&self, reader: &mut dyn Read) -> Result<ExplicitWithSignature, InputCodecError> {
148 let input = read_utf8(reader)?;
149 crate::parse_alto(&input).map_err(InputCodecError::codec)
150 }
151}
152
153fn read_utf8(reader: &mut dyn Read) -> Result<String, InputCodecError> {
154 let mut bytes = Vec::new();
155 reader.read_to_end(&mut bytes)?;
156 String::from_utf8(bytes).map_err(InputCodecError::Utf8)
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
161pub struct CodecMetadata {
162 pub name: &'static str,
164 pub description: &'static str,
166 pub extension: Option<&'static str>,
168}
169
170pub type RegisteredInputCodec<T> = dyn InputCodec<T> + Send + Sync;
172
173#[derive(Clone, Debug, PartialEq, Eq)]
175pub enum InputCodecRegistryError {
176 DuplicateName(String),
178 DuplicateExtension(String),
180}
181
182impl fmt::Display for InputCodecRegistryError {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 match self {
185 Self::DuplicateName(name) => {
186 write!(f, "duplicate input codec name {name:?} for one result type")
187 }
188 Self::DuplicateExtension(extension) => write!(
189 f,
190 "duplicate input codec extension {extension:?} for one result type"
191 ),
192 }
193 }
194}
195
196impl Error for InputCodecRegistryError {}
197
198#[derive(Default)]
200pub struct InputCodecRegistry {
201 codecs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
202}
203
204impl InputCodecRegistry {
205 pub fn new() -> Self {
207 Self::default()
208 }
209
210 pub fn standard() -> Self {
212 let mut registry = Self::new();
213 registry
214 .register::<Irtg, _>(IrtgInputCodec)
215 .expect("built-in input codec metadata is unique");
216 registry
217 .register::<Irtg, _>(TulipacInputCodec)
218 .expect("built-in input codec metadata is unique");
219 registry
220 .register::<ExplicitWithSignature, _>(AltoTreeAutomatonInputCodec)
221 .expect("built-in input codec metadata is unique");
222 registry
223 }
224
225 pub fn register<T: 'static, C>(&mut self, codec: C) -> Result<(), InputCodecRegistryError>
227 where
228 C: InputCodec<T> + 'static,
229 {
230 let codecs = self
231 .codecs
232 .entry(TypeId::of::<T>())
233 .or_insert_with(|| Box::new(Vec::<Box<RegisteredInputCodec<T>>>::new()))
234 .downcast_mut::<Vec<Box<RegisteredInputCodec<T>>>>()
235 .expect("a TypeId uniquely identifies its codec vector");
236 let metadata = codec.metadata();
237 let name = normalize_name(metadata.name);
238 if codecs
239 .iter()
240 .any(|registered| normalize_name(registered.metadata().name) == name)
241 {
242 return Err(InputCodecRegistryError::DuplicateName(name));
243 }
244 if let Some(extension) = metadata.extension.map(normalize_extension)
245 && codecs.iter().any(|registered| {
246 registered
247 .metadata()
248 .extension
249 .map(normalize_extension)
250 .as_deref()
251 == Some(extension.as_str())
252 })
253 {
254 return Err(InputCodecRegistryError::DuplicateExtension(extension));
255 }
256 codecs.push(Box::new(codec));
257 Ok(())
258 }
259
260 pub fn codecs_for<T: 'static>(&self) -> &[Box<RegisteredInputCodec<T>>] {
262 self.codecs
263 .get(&TypeId::of::<T>())
264 .and_then(|codecs| codecs.downcast_ref::<Vec<Box<RegisteredInputCodec<T>>>>())
265 .map_or(&[], Vec::as_slice)
266 }
267
268 pub fn codec_for_name<T: 'static>(
270 &self,
271 name: &str,
272 ) -> Result<&RegisteredInputCodec<T>, InputCodecError> {
273 let normalized = normalize_name(name);
274 self.codecs_for::<T>()
275 .iter()
276 .find(|codec| normalize_name(codec.metadata().name) == normalized)
277 .map(Box::as_ref)
278 .ok_or_else(|| InputCodecError::UnknownCodec(name.to_owned()))
279 }
280
281 pub fn codec_for_extension<T: 'static>(
283 &self,
284 extension: &str,
285 ) -> Result<&RegisteredInputCodec<T>, InputCodecError> {
286 let normalized = normalize_extension(extension);
287 self.codecs_for::<T>()
288 .iter()
289 .find(|codec| {
290 codec
291 .metadata()
292 .extension
293 .map(normalize_extension)
294 .as_deref()
295 == Some(normalized.as_str())
296 })
297 .map(Box::as_ref)
298 .ok_or_else(|| InputCodecError::UnknownCodec(extension.to_owned()))
299 }
300
301 pub fn codec_for_path<T: 'static>(
303 &self,
304 path: &Path,
305 ) -> Result<&RegisteredInputCodec<T>, InputCodecError> {
306 let extension = path
307 .extension()
308 .and_then(|extension| extension.to_str())
309 .ok_or(InputCodecError::MissingExtension)?;
310 self.codec_for_extension(extension)
311 }
312}
313
314fn normalize_name(name: &str) -> String {
315 name.trim().to_ascii_lowercase()
316}
317
318fn normalize_extension(extension: &str) -> String {
319 extension
320 .trim()
321 .trim_start_matches('.')
322 .to_ascii_lowercase()
323}
324
325pub trait OutputCodec<V: ?Sized> {
330 type Output;
332
333 fn metadata(&self) -> &'static CodecMetadata;
335
336 fn encode(&self, value: &V) -> Self::Output;
338}
339
340#[derive(Clone, Copy, Debug, Default)]
344pub struct DisplayCodec;
345
346impl<V: fmt::Display + ?Sized> OutputCodec<V> for DisplayCodec {
347 type Output = String;
348
349 fn metadata(&self) -> &'static CodecMetadata {
350 static METADATA: CodecMetadata = CodecMetadata {
351 name: "display",
352 description: "Default string representation",
353 extension: Some("txt"),
354 };
355 &METADATA
356 }
357
358 fn encode(&self, value: &V) -> Self::Output {
359 value.to_string()
360 }
361}
362
363#[derive(Clone, Copy, Debug, Default)]
367pub struct SpaceJoinCodec;
368
369impl OutputCodec<Vec<String>> for SpaceJoinCodec {
370 type Output = String;
371
372 fn metadata(&self) -> &'static CodecMetadata {
373 static METADATA: CodecMetadata = CodecMetadata {
374 name: "string",
375 description: "Space-separated string",
376 extension: Some("txt"),
377 };
378 &METADATA
379 }
380
381 #[allow(clippy::ptr_arg)] fn encode(&self, value: &Vec<String>) -> Self::Output {
383 value.join(" ")
384 }
385}
386
387#[derive(Debug)]
389pub enum VisualRepresentation {
390 Text(String),
392 Tree(TreeValue),
394 FeatureStructure(FeatureStructure),
396}
397
398#[derive(Clone, Copy, Debug, Default)]
400pub struct TextVisualizationCodec<C> {
401 codec: C,
402}
403
404impl<C> TextVisualizationCodec<C> {
405 pub fn new(codec: C) -> Self {
407 Self { codec }
408 }
409}
410
411impl<V: ?Sized, C> OutputCodec<V> for TextVisualizationCodec<C>
412where
413 C: OutputCodec<V, Output = String>,
414{
415 type Output = VisualRepresentation;
416
417 fn metadata(&self) -> &'static CodecMetadata {
418 self.codec.metadata()
419 }
420
421 fn encode(&self, value: &V) -> Self::Output {
422 VisualRepresentation::Text(self.codec.encode(value))
423 }
424}
425
426#[derive(Clone, Copy, Debug, Default)]
428pub struct TreeVisualizationCodec;
429
430impl OutputCodec<TreeValue> for TreeVisualizationCodec {
431 type Output = VisualRepresentation;
432
433 fn metadata(&self) -> &'static CodecMetadata {
434 static METADATA: CodecMetadata = CodecMetadata {
435 name: "tree-visualization",
436 description: "Tree visualization",
437 extension: None,
438 };
439 &METADATA
440 }
441
442 fn encode(&self, value: &TreeValue) -> Self::Output {
443 VisualRepresentation::Tree(value.clone())
444 }
445}
446
447#[derive(Clone, Copy, Debug, Default)]
449pub struct FeatureStructureVisualizationCodec;
450
451impl OutputCodec<FeatureStructure> for FeatureStructureVisualizationCodec {
452 type Output = VisualRepresentation;
453
454 fn metadata(&self) -> &'static CodecMetadata {
455 static METADATA: CodecMetadata = CodecMetadata {
456 name: "feature-structure-visualization",
457 description: "Feature-structure visualization",
458 extension: None,
459 };
460 &METADATA
461 }
462
463 fn encode(&self, value: &FeatureStructure) -> Self::Output {
464 VisualRepresentation::FeatureStructure(value.clone())
465 }
466}
467
468pub type TextOutputCodec<V> = dyn OutputCodec<V, Output = String> + Send + Sync;
470
471#[derive(Clone, Debug, PartialEq, Eq)]
473pub enum OutputCodecError {
474 UnknownCodec(String),
476}
477
478impl fmt::Display for OutputCodecError {
479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480 match self {
481 Self::UnknownCodec(name) => write!(f, "no output codec registered with name {name:?}"),
482 }
483 }
484}
485
486impl Error for OutputCodecError {}
487
488#[derive(Default)]
493pub struct OutputCodecRegistry {
494 codecs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
495}
496
497impl OutputCodecRegistry {
498 pub fn new() -> Self {
500 Self::default()
501 }
502
503 pub fn standard() -> Self {
505 let mut registry = Self::new();
506 registry.register::<Vec<String>, _>(SpaceJoinCodec);
507 registry.register::<TagStringValue<String>, _>(DisplayCodec);
508 registry.register::<TreeValue, _>(DisplayCodec);
509 registry.register::<FeatureStructure, _>(DisplayCodec);
510 registry
511 }
512
513 pub fn register<V: 'static, C>(&mut self, codec: C)
515 where
516 C: OutputCodec<V, Output = String> + Send + Sync + 'static,
517 {
518 self.codecs
519 .entry(TypeId::of::<V>())
520 .or_insert_with(|| Box::new(Vec::<Box<TextOutputCodec<V>>>::new()))
521 .downcast_mut::<Vec<Box<TextOutputCodec<V>>>>()
522 .expect("a TypeId uniquely identifies its codec vector")
523 .push(Box::new(codec));
524 }
525
526 pub fn codecs_for<V: 'static>(&self) -> &[Box<TextOutputCodec<V>>] {
530 self.codecs
531 .get(&TypeId::of::<V>())
532 .and_then(|codecs| codecs.downcast_ref::<Vec<Box<TextOutputCodec<V>>>>())
533 .map_or(&[], Vec::as_slice)
534 }
535
536 pub fn codec_for_name<V: 'static>(
538 &self,
539 name: &str,
540 ) -> Result<&TextOutputCodec<V>, OutputCodecError> {
541 let normalized = normalize_name(name);
542 self.codecs_for::<V>()
543 .iter()
544 .find(|codec| normalize_name(codec.metadata().name) == normalized)
545 .map(Box::as_ref)
546 .ok_or_else(|| OutputCodecError::UnknownCodec(name.to_owned()))
547 }
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553 use std::sync::{
554 Arc,
555 atomic::{AtomicUsize, Ordering},
556 };
557
558 #[derive(Clone)]
559 struct CountingInputCodec {
560 calls: Arc<AtomicUsize>,
561 metadata: &'static CodecMetadata,
562 }
563
564 impl InputCodec<u32> for CountingInputCodec {
565 fn metadata(&self) -> &'static CodecMetadata {
566 self.metadata
567 }
568
569 fn read(&self, reader: &mut dyn Read) -> Result<u32, InputCodecError> {
570 self.calls.fetch_add(1, Ordering::Relaxed);
571 let input = read_utf8(reader)?;
572 input.trim().parse().map_err(InputCodecError::codec)
573 }
574 }
575
576 static COUNTING_METADATA: CodecMetadata = CodecMetadata {
577 name: "count",
578 description: "Count",
579 extension: Some("count"),
580 };
581
582 static DUPLICATE_NAME_METADATA: CodecMetadata = CodecMetadata {
583 name: "COUNT",
584 description: "Duplicate count",
585 extension: Some("other"),
586 };
587
588 static DUPLICATE_EXTENSION_METADATA: CodecMetadata = CodecMetadata {
589 name: "other",
590 description: "Duplicate extension",
591 extension: Some(".COUNT"),
592 };
593
594 #[test]
595 fn standard_input_registry_is_partitioned_by_exact_result_type() {
596 let registry = InputCodecRegistry::standard();
597 let irtg_extensions = registry
598 .codecs_for::<Irtg>()
599 .iter()
600 .filter_map(|codec| codec.metadata().extension)
601 .collect::<Vec<_>>();
602 assert_eq!(irtg_extensions, vec!["irtg", "tag"]);
603
604 let auto_extensions = registry
605 .codecs_for::<ExplicitWithSignature>()
606 .iter()
607 .filter_map(|codec| codec.metadata().extension)
608 .collect::<Vec<_>>();
609 assert_eq!(auto_extensions, vec!["auto"]);
610 assert!(registry.codecs_for::<u32>().is_empty());
611 }
612
613 #[test]
614 fn input_registry_lookup_is_normalized_and_lazy() {
615 let calls = Arc::new(AtomicUsize::new(0));
616 let mut registry = InputCodecRegistry::new();
617 registry
618 .register::<u32, _>(CountingInputCodec {
619 calls: calls.clone(),
620 metadata: &COUNTING_METADATA,
621 })
622 .unwrap();
623
624 assert_eq!(
625 registry
626 .codec_for_name::<u32>("COUNT")
627 .unwrap()
628 .metadata()
629 .name,
630 "count"
631 );
632 assert_eq!(
633 registry
634 .codec_for_extension::<u32>(".CoUnT")
635 .unwrap()
636 .metadata()
637 .extension,
638 Some("count")
639 );
640 assert_eq!(
641 registry
642 .codec_for_path::<u32>(Path::new("value.COUNT"))
643 .unwrap()
644 .metadata()
645 .name,
646 "count"
647 );
648 assert_eq!(calls.load(Ordering::Relaxed), 0);
649 assert_eq!(
650 registry
651 .codec_for_extension::<u32>("count")
652 .unwrap()
653 .decode("17")
654 .unwrap(),
655 17
656 );
657 assert_eq!(calls.load(Ordering::Relaxed), 1);
658 assert!(matches!(
659 registry.codec_for_path::<u32>(Path::new("no-extension")),
660 Err(InputCodecError::MissingExtension)
661 ));
662 assert!(matches!(
663 registry.codec_for_extension::<u32>("unknown"),
664 Err(InputCodecError::UnknownCodec(_))
665 ));
666 }
667
668 #[test]
669 fn input_registry_rejects_duplicate_names_and_extensions_per_type() {
670 let calls = Arc::new(AtomicUsize::new(0));
671 let mut registry = InputCodecRegistry::new();
672 registry
673 .register::<u32, _>(CountingInputCodec {
674 calls: calls.clone(),
675 metadata: &COUNTING_METADATA,
676 })
677 .unwrap();
678 assert_eq!(
679 registry.register::<u32, _>(CountingInputCodec {
680 calls: calls.clone(),
681 metadata: &DUPLICATE_NAME_METADATA,
682 }),
683 Err(InputCodecRegistryError::DuplicateName("count".to_owned()))
684 );
685 assert_eq!(
686 registry.register::<u32, _>(CountingInputCodec {
687 calls,
688 metadata: &DUPLICATE_EXTENSION_METADATA,
689 }),
690 Err(InputCodecRegistryError::DuplicateExtension(
691 "count".to_owned()
692 ))
693 );
694 assert!(
695 registry.register::<u64, _>(U64InputCodec).is_ok(),
696 "the same metadata is allowed for another result type"
697 );
698 }
699
700 struct U64InputCodec;
701
702 impl InputCodec<u64> for U64InputCodec {
703 fn metadata(&self) -> &'static CodecMetadata {
704 &COUNTING_METADATA
705 }
706
707 fn read(&self, reader: &mut dyn Read) -> Result<u64, InputCodecError> {
708 read_utf8(reader)?
709 .trim()
710 .parse()
711 .map_err(InputCodecError::codec)
712 }
713 }
714
715 #[test]
716 fn built_in_input_codecs_decode_expected_types() {
717 let irtg = IrtgInputCodec
718 .decode(
719 "interpretation string: de.up.ling.irtg.algebra.StringAlgebra\n\
720 S! -> word\n[string] hello\n",
721 )
722 .unwrap();
723 assert!(irtg.interpretation_ref("string").is_some());
724
725 let auto = AltoTreeAutomatonInputCodec
726 .decode("S! -> f(A)\nA -> a")
727 .unwrap();
728 assert_eq!(auto.states.resolve(crate::StateId(0)), "S");
729 assert!(auto.signature.get("f").is_some());
730 assert_eq!(auto.automaton.rules().count(), 2);
731 }
732
733 #[test]
734 fn display_codec_uses_display() {
735 assert_eq!(DisplayCodec.encode(&2u8), "2");
736 assert_eq!(DisplayCodec.encode("hi"), "hi");
737 assert_eq!(
738 <DisplayCodec as OutputCodec<u8>>::metadata(&DisplayCodec).extension,
739 Some("txt")
740 );
741 }
742
743 #[test]
744 fn space_join_codec_joins_words() {
745 assert_eq!(
746 SpaceJoinCodec.encode(&vec!["the".to_owned(), "woman".to_owned()]),
747 "the woman"
748 );
749 assert_eq!(SpaceJoinCodec.encode(&vec![]), "");
750 assert_eq!(SpaceJoinCodec.encode(&vec!["the".to_owned()]), "the");
751 }
752
753 #[test]
754 fn text_visualization_wraps_text() {
755 let codec = TextVisualizationCodec::new(SpaceJoinCodec);
756 let value = vec!["hello".to_owned(), "world".to_owned()];
757 assert!(matches!(
758 codec.encode(&value),
759 VisualRepresentation::Text(text) if text == "hello world"
760 ));
761 }
762
763 #[test]
764 fn registry_uses_exact_types_and_does_not_encode_during_lookup() {
765 struct CountingCodec(Arc<AtomicUsize>);
766
767 impl OutputCodec<u32> for CountingCodec {
768 type Output = String;
769
770 fn metadata(&self) -> &'static CodecMetadata {
771 static METADATA: CodecMetadata = CodecMetadata {
772 name: "counting",
773 description: "Counting codec",
774 extension: Some("count"),
775 };
776 &METADATA
777 }
778
779 fn encode(&self, value: &u32) -> Self::Output {
780 self.0.fetch_add(1, Ordering::Relaxed);
781 value.to_string()
782 }
783 }
784
785 let calls = Arc::new(AtomicUsize::new(0));
786 let mut registry = OutputCodecRegistry::new();
787 registry.register::<u32, _>(CountingCodec(calls.clone()));
788
789 let codecs = registry.codecs_for::<u32>();
790 assert_eq!(codecs.len(), 1);
791 assert_eq!(codecs[0].metadata().extension, Some("count"));
792 assert!(registry.codecs_for::<u64>().is_empty());
793 assert_eq!(calls.load(Ordering::Relaxed), 0);
794
795 assert_eq!(registry.codec_for_name::<u32>("COUNTING").unwrap().encode(&7), "7");
796 assert_eq!(calls.load(Ordering::Relaxed), 1);
797 assert!(matches!(
798 registry.codec_for_name::<u64>("counting"),
799 Err(OutputCodecError::UnknownCodec(_))
800 ));
801 }
802}