1use crate::{
9 CodecMetadata, InputCodec, InputCodecError, Irtg, IrtgError,
10 alto_ast::{
11 AstAutoRule, AstHomRule, AstHomTerm, AstInterpretationDecl, AstIrtg, AstRule, AstState,
12 },
13 irtg::build_irtg,
14};
15use packed_term_arena::tree::{Tree, TreeArena};
16use std::{
17 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
18 fmt, fs,
19 io::Read,
20 path::{Path, PathBuf},
21};
22
23const TAG_STRING_CLASS: &str = "de.up.ling.irtg.algebra.TagStringAlgebra";
24const TAG_TREE_CLASS: &str = "de.up.ling.irtg.algebra.TagTreeAlgebra";
25const FEATURE_STRUCTURE_CLASS: &str = "de.up.ling.irtg.algebra.FeatureStructureAlgebra";
26
27const CONC11: &str = "*CONC11*";
28const CONC12: &str = "*CONC12*";
29const CONC21: &str = "*CONC21*";
30const WRAP21: &str = "*WRAP21*";
31const WRAP22: &str = "*WRAP22*";
32const EE: &str = "*EE*";
33const SUBSTITUTE: &str = "@";
34const HOLE: &str = "*";
35
36#[derive(Clone, Debug, Default)]
38pub struct TulipacInputCodec;
39
40impl TulipacInputCodec {
41 pub fn new() -> Self {
43 Self
44 }
45
46 fn read_path_inner(&self, path: &Path) -> Result<Irtg, TulipacError> {
47 let mut declarations = Declarations::default();
48 let mut include_stack = Vec::new();
49 parse_path(path, &mut declarations, &mut include_stack)?;
50 compile(declarations)
51 }
52}
53
54impl InputCodec<Irtg> for TulipacInputCodec {
55 fn metadata(&self) -> &'static CodecMetadata {
56 static METADATA: CodecMetadata = CodecMetadata {
57 name: "tulipac",
58 description: "TAG grammar (Tulipac format)",
59 extension: Some("tag"),
60 };
61 &METADATA
62 }
63
64 fn read(&self, reader: &mut dyn Read) -> Result<Irtg, InputCodecError> {
65 let mut bytes = Vec::new();
66 reader.read_to_end(&mut bytes)?;
67 let input = String::from_utf8(bytes)?;
68 let declarations = Parser::new(&input)
69 .and_then(Parser::parse_grammar)
70 .map_err(InputCodecError::codec)?;
71 if let Some(include) = declarations.includes.first() {
72 return Err(InputCodecError::codec(TulipacError::Semantic(format!(
73 "#include {include:?} requires TulipacInputCodec::read_path"
74 ))));
75 }
76 compile(declarations).map_err(InputCodecError::codec)
77 }
78
79 fn read_path(&self, path: &Path) -> Result<Irtg, InputCodecError> {
80 self.read_path_inner(path).map_err(InputCodecError::codec)
81 }
82}
83
84#[derive(Debug)]
86pub enum TulipacError {
87 Io {
89 path: PathBuf,
91 source: std::io::Error,
93 },
94 Lex {
96 offset: usize,
98 message: String,
100 },
101 Parse {
103 offset: usize,
105 message: String,
107 },
108 Semantic(String),
110 Irtg(IrtgError),
112}
113
114impl fmt::Display for TulipacError {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 Self::Io { path, source } => write!(f, "cannot read {}: {source}", path.display()),
118 Self::Lex { offset, message } => {
119 write!(f, "Tulipac lexical error at byte {offset}: {message}")
120 }
121 Self::Parse { offset, message } => {
122 write!(f, "Tulipac syntax error at byte {offset}: {message}")
123 }
124 Self::Semantic(message) => write!(f, "invalid Tulipac grammar: {message}"),
125 Self::Irtg(error) => write!(f, "cannot construct IRTG: {error}"),
126 }
127 }
128}
129
130impl std::error::Error for TulipacError {
131 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
132 match self {
133 Self::Io { source, .. } => Some(source),
134 Self::Irtg(error) => Some(error),
135 _ => None,
136 }
137 }
138}
139
140impl From<IrtgError> for TulipacError {
141 fn from(value: IrtgError) -> Self {
142 Self::Irtg(value)
143 }
144}
145
146#[derive(Clone, Debug, PartialEq, Eq)]
147enum TokenKind {
148 Tree,
149 Family,
150 Word,
151 Lemma,
152 Include,
153 Name(String),
154 FamilyName(String),
155 Annotation(String),
156 Variable(String),
157 Bang,
158 Star,
159 Plus,
160 Colon,
161 LBrace,
162 RBrace,
163 LBracket,
164 RBracket,
165 Comma,
166 Equals,
167}
168
169#[derive(Clone, Debug)]
170struct Token {
171 kind: TokenKind,
172 offset: usize,
173}
174
175fn lex(input: &str) -> Result<Vec<Token>, TulipacError> {
176 let mut tokens = Vec::new();
177 let mut chars = input.char_indices().peekable();
178
179 while let Some((offset, ch)) = chars.next() {
180 if ch.is_whitespace() {
181 continue;
182 }
183 if ch == '/' && chars.peek().is_some_and(|&(_, next)| next == '/') {
184 chars.next();
185 for (_, next) in chars.by_ref() {
186 if next == '\n' {
187 break;
188 }
189 }
190 continue;
191 }
192 if ch == '/' && chars.peek().is_some_and(|&(_, next)| next == '*') {
193 chars.next();
194 let mut previous = '\0';
195 let mut closed = false;
196 for (_, next) in chars.by_ref() {
197 if previous == '*' && next == '/' {
198 closed = true;
199 break;
200 }
201 previous = next;
202 }
203 if !closed {
204 return Err(TulipacError::Lex {
205 offset,
206 message: "unterminated block comment".to_owned(),
207 });
208 }
209 continue;
210 }
211
212 let kind = match ch {
213 '\'' | '"' => {
214 let quote = ch;
215 let mut value = String::new();
216 let mut closed = false;
217 for (_, next) in chars.by_ref() {
218 if next == quote {
219 closed = true;
220 break;
221 }
222 value.push(next);
223 }
224 if !closed {
225 return Err(TulipacError::Lex {
226 offset,
227 message: "unterminated quoted identifier".to_owned(),
228 });
229 }
230 TokenKind::Name(value)
231 }
232 '<' => {
233 let mut value = String::new();
234 let mut closed = false;
235 for (_, next) in chars.by_ref() {
236 if next == '>' {
237 closed = true;
238 break;
239 }
240 value.push(next);
241 }
242 if !closed {
243 return Err(TulipacError::Lex {
244 offset,
245 message: "unterminated family identifier".to_owned(),
246 });
247 }
248 TokenKind::FamilyName(value)
249 }
250 '@' | '?' => {
251 let mut value = String::new();
252 while let Some(&(_, next)) = chars.peek() {
253 if next.is_ascii_alphanumeric() || next == '_' {
254 value.push(next);
255 chars.next();
256 } else {
257 break;
258 }
259 }
260 if value.is_empty() {
261 return Err(TulipacError::Lex {
262 offset,
263 message: format!("{ch} must be followed by an identifier"),
264 });
265 }
266 if ch == '@' {
267 TokenKind::Annotation(value)
268 } else {
269 TokenKind::Variable(value)
270 }
271 }
272 '#' => {
273 let mut word = String::from("#");
274 while let Some(&(_, next)) = chars.peek() {
275 if next.is_ascii_alphabetic() {
276 word.push(next);
277 chars.next();
278 } else {
279 break;
280 }
281 }
282 if word == "#include" {
283 TokenKind::Include
284 } else {
285 return Err(TulipacError::Lex {
286 offset,
287 message: format!("unknown directive {word:?}"),
288 });
289 }
290 }
291 '!' => TokenKind::Bang,
292 '*' => TokenKind::Star,
293 '+' => TokenKind::Plus,
294 ':' => TokenKind::Colon,
295 '{' => TokenKind::LBrace,
296 '}' => TokenKind::RBrace,
297 '[' => TokenKind::LBracket,
298 ']' => TokenKind::RBracket,
299 ',' => TokenKind::Comma,
300 '=' => TokenKind::Equals,
301 c if c.is_ascii_alphabetic() || c == '_' => {
302 let mut value = String::new();
303 value.push(c);
304 while let Some(&(_, next)) = chars.peek() {
305 if next.is_ascii_alphanumeric() || next == '_' {
306 value.push(next);
307 chars.next();
308 } else {
309 break;
310 }
311 }
312 match value.as_str() {
313 "tree" => TokenKind::Tree,
314 "family" => TokenKind::Family,
315 "word" => TokenKind::Word,
316 "lemma" => TokenKind::Lemma,
317 _ => TokenKind::Name(value),
318 }
319 }
320 _ => {
321 return Err(TulipacError::Lex {
322 offset,
323 message: format!("unexpected character {ch:?}"),
324 });
325 }
326 };
327 tokens.push(Token { kind, offset });
328 }
329
330 Ok(tokens)
331}
332
333#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
334struct FeatureMap(BTreeMap<String, FeatureAtom>);
335
336#[derive(Clone, Debug, PartialEq, Eq, Hash)]
337enum FeatureAtom {
338 Constant(String),
339 Variable(String),
340}
341
342impl FeatureMap {
343 fn merge(&self, other: &Self) -> Result<Self, TulipacError> {
344 let mut result = self.0.clone();
345 for (attribute, value) in &other.0 {
346 match (result.get(attribute), value) {
347 (None, _) => {
348 result.insert(attribute.clone(), value.clone());
349 }
350 (Some(existing), candidate) if existing == candidate => {}
351 (Some(FeatureAtom::Variable(_)), candidate) => {
352 result.insert(attribute.clone(), candidate.clone());
353 }
354 (Some(_), FeatureAtom::Variable(_)) => {}
355 (Some(existing), candidate) => {
356 return Err(TulipacError::Semantic(format!(
357 "feature clash for {attribute:?}: {existing:?} versus {candidate:?}"
358 )));
359 }
360 }
361 }
362 Ok(Self(result))
363 }
364
365 fn safe_suffix(&self) -> String {
366 if self.0.is_empty() {
367 return String::new();
368 }
369 let mut raw = String::new();
370 for (attribute, value) in &self.0 {
371 raw.push('[');
372 raw.push_str(attribute);
373 raw.push('=');
374 match value {
375 FeatureAtom::Constant(value) => raw.push_str(value),
376 FeatureAtom::Variable(value) => {
377 raw.push('?');
378 raw.push_str(value);
379 }
380 }
381 raw.push(']');
382 }
383 raw.chars()
384 .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
385 .collect()
386 }
387}
388
389#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390enum NodeType {
391 Default,
392 Substitution,
393 Foot,
394 Head,
395}
396
397#[derive(Clone, Debug)]
398struct Node {
399 label: String,
400 node_type: NodeType,
401 no_adjunction: bool,
402 top: Option<FeatureMap>,
403 bottom: Option<FeatureMap>,
404}
405
406#[derive(Debug)]
407struct ElementaryTree {
408 arena: TreeArena<Node>,
409 root: Tree,
410}
411
412impl ElementaryTree {
413 fn is_auxiliary(&self) -> bool {
414 self.arena
415 .post_order(self.root)
416 .any(|node| self.arena.get_label(node).node_type == NodeType::Foot)
417 }
418}
419
420#[derive(Clone, Debug, PartialEq, Eq, Hash)]
421struct LexiconEntry {
422 word: String,
423 tree: String,
424 features: FeatureMap,
425}
426
427#[derive(Debug, Default)]
428struct Declarations {
429 trees: Vec<(String, ElementaryTree)>,
430 families: Vec<(String, Vec<String>)>,
431 words: Vec<WordDecl>,
432 lemmas: Vec<LemmaDecl>,
433 includes: Vec<String>,
434}
435
436impl Declarations {
437 fn extend(&mut self, mut other: Self) {
438 self.trees.append(&mut other.trees);
439 self.families.append(&mut other.families);
440 self.words.append(&mut other.words);
441 self.lemmas.append(&mut other.lemmas);
442 self.includes.append(&mut other.includes);
443 }
444}
445
446#[derive(Clone, Debug)]
447enum TreeRef {
448 Tree(String),
449 Family(String),
450}
451
452#[derive(Clone, Debug)]
453struct WordDecl {
454 word: String,
455 target: TreeRef,
456 features: FeatureMap,
457}
458
459#[derive(Clone, Debug)]
460struct LemmaDecl {
461 lemma: String,
462 target: TreeRef,
463 features: FeatureMap,
464 words: Vec<(String, FeatureMap)>,
465}
466
467struct Parser {
468 tokens: Vec<Token>,
469 position: usize,
470}
471
472impl Parser {
473 fn new(input: &str) -> Result<Self, TulipacError> {
474 Ok(Self {
475 tokens: lex(input)?,
476 position: 0,
477 })
478 }
479
480 fn parse_grammar(mut self) -> Result<Declarations, TulipacError> {
481 let mut declarations = Declarations::default();
482 while let Some(token) = self.peek() {
483 match token.kind {
484 TokenKind::Tree => declarations.trees.push(self.parse_tree()?),
485 TokenKind::Family => declarations.families.push(self.parse_family()?),
486 TokenKind::Word => declarations.words.push(self.parse_word()?),
487 TokenKind::Lemma => declarations.lemmas.push(self.parse_lemma()?),
488 TokenKind::Include => declarations.includes.push(self.parse_include()?),
489 _ => return self.error("expected tree, family, word, lemma, or #include"),
490 }
491 }
492 Ok(declarations)
493 }
494
495 fn parse_tree(&mut self) -> Result<(String, ElementaryTree), TulipacError> {
496 self.expect_simple(TokenKind::Tree)?;
497 let name = self.identifier()?;
498 self.expect_simple(TokenKind::Colon)?;
499 let mut arena = TreeArena::new();
500 let root = self.node(&mut arena)?;
501 Ok((name, ElementaryTree { arena, root }))
502 }
503
504 fn node(&mut self, arena: &mut TreeArena<Node>) -> Result<Tree, TulipacError> {
505 let label = self.identifier()?;
506 let node_type = match self.peek().map(|token| &token.kind) {
507 Some(TokenKind::Bang) => {
508 self.position += 1;
509 NodeType::Substitution
510 }
511 Some(TokenKind::Star) => {
512 self.position += 1;
513 NodeType::Foot
514 }
515 Some(TokenKind::Plus) => {
516 self.position += 1;
517 NodeType::Head
518 }
519 _ => NodeType::Default,
520 };
521 let no_adjunction = match self.peek().map(|token| &token.kind) {
522 Some(TokenKind::Annotation(annotation)) => {
523 let no_adjunction = annotation == "NA";
524 self.position += 1;
525 no_adjunction
526 }
527 _ => false,
528 };
529 let top = if self.at(&TokenKind::LBracket) {
530 Some(self.feature_map()?)
531 } else {
532 None
533 };
534 let bottom = if self.at(&TokenKind::LBracket) {
535 Some(self.feature_map()?)
536 } else {
537 None
538 };
539 let mut children = Vec::new();
540 if self.consume(&TokenKind::LBrace) {
541 while !self.consume(&TokenKind::RBrace) {
542 if self.peek().is_none() {
543 return self.error("unterminated node child block");
544 }
545 children.push(self.node(arena)?);
546 }
547 }
548 Ok(arena.add_node(
549 Node {
550 label,
551 node_type,
552 no_adjunction,
553 top,
554 bottom,
555 },
556 children,
557 ))
558 }
559
560 fn parse_family(&mut self) -> Result<(String, Vec<String>), TulipacError> {
561 self.expect_simple(TokenKind::Family)?;
562 let name = self.identifier()?;
563 self.expect_simple(TokenKind::Colon)?;
564 self.expect_simple(TokenKind::LBrace)?;
565 let mut trees = vec![self.identifier()?];
566 while self.consume(&TokenKind::Comma) {
567 trees.push(self.identifier()?);
568 }
569 self.expect_simple(TokenKind::RBrace)?;
570 Ok((name, trees))
571 }
572
573 fn parse_word(&mut self) -> Result<WordDecl, TulipacError> {
574 self.expect_simple(TokenKind::Word)?;
575 let word = self.identifier()?;
576 self.expect_simple(TokenKind::Colon)?;
577 let target = self.tree_ref()?;
578 let features = if self.at(&TokenKind::LBracket) {
579 self.feature_map()?
580 } else {
581 FeatureMap::default()
582 };
583 Ok(WordDecl {
584 word,
585 target,
586 features,
587 })
588 }
589
590 fn parse_lemma(&mut self) -> Result<LemmaDecl, TulipacError> {
591 self.expect_simple(TokenKind::Lemma)?;
592 let lemma = self.identifier()?;
593 self.expect_simple(TokenKind::Colon)?;
594 let target = self.tree_ref()?;
595 let features = if self.at(&TokenKind::LBracket) {
596 self.feature_map()?
597 } else {
598 FeatureMap::default()
599 };
600 self.expect_simple(TokenKind::LBrace)?;
601 let mut words = Vec::new();
602 while !self.consume(&TokenKind::RBrace) {
603 self.expect_simple(TokenKind::Word)?;
604 let word = self.identifier()?;
605 let word_features = if self.consume(&TokenKind::Colon) {
606 self.feature_map()?
607 } else {
608 FeatureMap::default()
609 };
610 words.push((word, word_features));
611 }
612 if words.is_empty() {
613 return self.error("lemma must contain at least one word");
614 }
615 Ok(LemmaDecl {
616 lemma,
617 target,
618 features,
619 words,
620 })
621 }
622
623 fn parse_include(&mut self) -> Result<String, TulipacError> {
624 self.expect_simple(TokenKind::Include)?;
625 self.identifier()
626 }
627
628 fn feature_map(&mut self) -> Result<FeatureMap, TulipacError> {
629 self.expect_simple(TokenKind::LBracket)?;
630 let mut features = BTreeMap::new();
631 if self.consume(&TokenKind::RBracket) {
632 return Ok(FeatureMap(features));
633 }
634 loop {
635 let attribute = self.identifier()?;
636 self.expect_simple(TokenKind::Equals)?;
637 let value = match self.next() {
638 Some(Token {
639 kind: TokenKind::Name(value),
640 ..
641 }) => FeatureAtom::Constant(value),
642 Some(Token {
643 kind: TokenKind::Variable(value),
644 ..
645 }) => FeatureAtom::Variable(value),
646 Some(token) => {
647 return Err(TulipacError::Parse {
648 offset: token.offset,
649 message: "expected feature value or variable".to_owned(),
650 });
651 }
652 None => return self.error("expected feature value or variable"),
653 };
654 features.insert(attribute, value);
655 if self.consume(&TokenKind::RBracket) {
656 break;
657 }
658 self.expect_simple(TokenKind::Comma)?;
659 }
660 Ok(FeatureMap(features))
661 }
662
663 fn tree_ref(&mut self) -> Result<TreeRef, TulipacError> {
664 match self.next() {
665 Some(Token {
666 kind: TokenKind::Name(name),
667 ..
668 }) => Ok(TreeRef::Tree(name)),
669 Some(Token {
670 kind: TokenKind::FamilyName(name),
671 ..
672 }) => Ok(TreeRef::Family(name)),
673 Some(token) => Err(TulipacError::Parse {
674 offset: token.offset,
675 message: "expected elementary-tree or family name".to_owned(),
676 }),
677 None => self.error("expected elementary-tree or family name"),
678 }
679 }
680
681 fn identifier(&mut self) -> Result<String, TulipacError> {
682 match self.next() {
683 Some(Token {
684 kind: TokenKind::Name(name),
685 ..
686 }) => Ok(name),
687 Some(token) => Err(TulipacError::Parse {
688 offset: token.offset,
689 message: "expected identifier".to_owned(),
690 }),
691 None => self.error("expected identifier"),
692 }
693 }
694
695 fn peek(&self) -> Option<&Token> {
696 self.tokens.get(self.position)
697 }
698
699 fn next(&mut self) -> Option<Token> {
700 let token = self.tokens.get(self.position).cloned();
701 if token.is_some() {
702 self.position += 1;
703 }
704 token
705 }
706
707 fn at(&self, kind: &TokenKind) -> bool {
708 self.peek().is_some_and(|token| &token.kind == kind)
709 }
710
711 fn consume(&mut self, kind: &TokenKind) -> bool {
712 if self.at(kind) {
713 self.position += 1;
714 true
715 } else {
716 false
717 }
718 }
719
720 fn expect_simple(&mut self, kind: TokenKind) -> Result<(), TulipacError> {
721 match self.next() {
722 Some(token) if token.kind == kind => Ok(()),
723 Some(token) => Err(TulipacError::Parse {
724 offset: token.offset,
725 message: format!("expected {kind:?}, found {:?}", token.kind),
726 }),
727 None => self.error(&format!("expected {kind:?}")),
728 }
729 }
730
731 fn error<T>(&self, message: &str) -> Result<T, TulipacError> {
732 Err(TulipacError::Parse {
733 offset: self.peek().map_or(0, |token| token.offset),
734 message: message.to_owned(),
735 })
736 }
737}
738
739fn parse_path(
740 path: &Path,
741 declarations: &mut Declarations,
742 include_stack: &mut Vec<PathBuf>,
743) -> Result<(), TulipacError> {
744 let canonical = path.canonicalize().map_err(|source| TulipacError::Io {
745 path: path.to_owned(),
746 source,
747 })?;
748 if include_stack.contains(&canonical) {
749 return Err(TulipacError::Semantic(format!(
750 "cyclic #include involving {}",
751 canonical.display()
752 )));
753 }
754 include_stack.push(canonical.clone());
755 let input = fs::read_to_string(&canonical).map_err(|source| TulipacError::Io {
756 path: canonical.clone(),
757 source,
758 })?;
759 let mut parsed = Parser::new(&input)?.parse_grammar()?;
760 let base = canonical.parent().unwrap_or_else(|| Path::new("."));
761 for include in std::mem::take(&mut parsed.includes) {
762 parse_path(&base.join(include), declarations, include_stack)?;
763 }
764 declarations.extend(parsed);
765 include_stack.pop();
766 Ok(())
767}
768
769fn compile(declarations: Declarations) -> Result<Irtg, TulipacError> {
770 let mut trees = HashMap::new();
771 for (name, tree) in declarations.trees {
772 trees.insert(name, tree);
773 }
774 let mut families = HashMap::new();
775 for (name, members) in declarations.families {
776 families.insert(name, members);
777 }
778
779 let resolve = |target: &TreeRef, context: &str| -> Result<Vec<String>, TulipacError> {
780 match target {
781 TreeRef::Tree(tree) => {
782 if trees.contains_key(tree) {
783 Ok(vec![tree.clone()])
784 } else {
785 Err(TulipacError::Semantic(format!(
786 "{context} references unknown elementary tree {tree:?}"
787 )))
788 }
789 }
790 TreeRef::Family(family) => {
791 let members = families.get(family).ok_or_else(|| {
792 TulipacError::Semantic(format!(
793 "{context} references unknown tree family {family:?}"
794 ))
795 })?;
796 for tree in members {
797 if !trees.contains_key(tree) {
798 return Err(TulipacError::Semantic(format!(
799 "{context} references family {family:?}, which contains unknown tree {tree:?}"
800 )));
801 }
802 }
803 Ok(members.clone())
804 }
805 }
806 };
807
808 let mut lexicon = HashSet::new();
809 for word in declarations.words {
810 for tree in resolve(&word.target, &format!("word {:?}", word.word))? {
811 lexicon.insert(LexiconEntry {
812 word: word.word.clone(),
813 tree,
814 features: word.features.clone(),
815 });
816 }
817 }
818 for lemma in declarations.lemmas {
819 let tree_names = resolve(&lemma.target, &format!("lemma {:?}", lemma.lemma))?;
820 for (word, word_features) in lemma.words {
821 let features = lemma.features.merge(&word_features)?;
822 for tree in &tree_names {
823 lexicon.insert(LexiconEntry {
824 word: word.clone(),
825 tree: tree.clone(),
826 features: features.clone(),
827 });
828 }
829 }
830 }
831
832 let has_features = trees.values().any(|tree| {
833 tree.arena.post_order(tree.root).any(|node| {
834 let node = tree.arena.get_label(node);
835 node.top.is_some() || node.bottom.is_some()
836 })
837 });
838 let mut rules = Vec::new();
839 let mut adjunction_states = BTreeSet::new();
840 for entry in lexicon {
841 let tree = &trees[&entry.tree];
842 let mut child_states = Vec::new();
843 let tree_hom = tree_term(
844 tree,
845 tree.root,
846 &entry,
847 &mut child_states,
848 &mut adjunction_states,
849 );
850 let string_hom = string_term(&tree_hom, &child_states)?;
851 let feature_hom = has_features
852 .then(|| feature_term(tree, &tree_hom, &child_states, &entry))
853 .transpose()?;
854 let parent = if tree.is_auxiliary() {
855 state_name(&tree.arena.get_label(tree.root).label, 'A')
856 } else {
857 state_name(&tree.arena.get_label(tree.root).label, 'S')
858 };
859 let symbol = format!(
860 "{}-{}{}",
861 entry.tree,
862 entry.word,
863 entry.features.safe_suffix()
864 );
865 rules.push(AstRule {
866 auto: AstAutoRule {
867 parent: AstState {
868 name: parent,
869 is_final: false,
870 },
871 symbol,
872 children: child_states
873 .iter()
874 .map(|name| AstState {
875 name: name.clone(),
876 is_final: false,
877 })
878 .collect(),
879 weight: None,
880 },
881 homs: {
882 let mut homs = vec![
883 AstHomRule {
884 interpretation: "tree".to_owned(),
885 term: tree_hom,
886 },
887 AstHomRule {
888 interpretation: "string".to_owned(),
889 term: string_hom,
890 },
891 ];
892 if let Some(term) = feature_hom {
893 homs.push(AstHomRule {
894 interpretation: "ft".to_owned(),
895 term,
896 });
897 }
898 homs
899 },
900 });
901 }
902
903 for state in adjunction_states {
904 rules.push(AstRule {
905 auto: AstAutoRule {
906 parent: AstState {
907 name: state.clone(),
908 is_final: false,
909 },
910 symbol: format!("*NOP*_{state}"),
911 children: Vec::new(),
912 weight: None,
913 },
914 homs: {
915 let mut homs = vec![
916 AstHomRule {
917 interpretation: "tree".to_owned(),
918 term: AstHomTerm::Symbol(HOLE.to_owned(), Vec::new()),
919 },
920 AstHomRule {
921 interpretation: "string".to_owned(),
922 term: AstHomTerm::Symbol(EE.to_owned(), Vec::new()),
923 },
924 ];
925 if has_features {
926 homs.push(AstHomRule {
927 interpretation: "ft".to_owned(),
928 term: AstHomTerm::Symbol("[foot: #1 [], root: #1]".to_owned(), Vec::new()),
929 });
930 }
931 homs
932 },
933 });
934 }
935
936 if !rules.iter().any(|rule| rule.auto.parent.name == "S_S") {
937 return Err(TulipacError::Semantic(
938 "grammar has no initial tree rooted in S".to_owned(),
939 ));
940 }
941 for rule in &mut rules {
942 if rule.auto.parent.name == "S_S" {
943 rule.auto.parent.is_final = true;
944 }
945 }
946
947 build_irtg(AstIrtg {
948 interpretations: {
949 let mut interpretations = vec![
950 AstInterpretationDecl {
951 name: "string".to_owned(),
952 algebra: TAG_STRING_CLASS.to_owned(),
953 },
954 AstInterpretationDecl {
955 name: "tree".to_owned(),
956 algebra: TAG_TREE_CLASS.to_owned(),
957 },
958 ];
959 if has_features {
960 interpretations.push(AstInterpretationDecl {
961 name: "ft".to_owned(),
962 algebra: FEATURE_STRUCTURE_CLASS.to_owned(),
963 });
964 }
965 interpretations
966 },
967 features: Vec::new(),
968 rules,
969 })
970 .map_err(Into::into)
971}
972
973fn state_name(label: &str, sort: char) -> String {
974 format!("{label}_{sort}")
975}
976
977fn tree_term(
978 tree: &ElementaryTree,
979 node: Tree,
980 entry: &LexiconEntry,
981 child_states: &mut Vec<String>,
982 adjunction_states: &mut BTreeSet<String>,
983) -> AstHomTerm {
984 let data = tree.arena.get_label(node);
985 let mut children: Vec<_> = tree
986 .arena
987 .get_children(node)
988 .iter()
989 .map(|&child| tree_term(tree, child, entry, child_states, adjunction_states))
990 .collect();
991
992 match data.node_type {
993 NodeType::Head => {
994 children = vec![AstHomTerm::Symbol(entry.word.clone(), Vec::new())];
995 ordinary_or_adjunction(data, children, child_states, adjunction_states)
996 }
997 NodeType::Foot => AstHomTerm::Symbol(HOLE.to_owned(), Vec::new()),
998 NodeType::Substitution => {
999 child_states.push(state_name(&data.label, 'S'));
1000 AstHomTerm::Variable(child_states.len())
1001 }
1002 NodeType::Default => {
1003 ordinary_or_adjunction(data, children, child_states, adjunction_states)
1004 }
1005 }
1006}
1007
1008fn ordinary_or_adjunction(
1009 node: &Node,
1010 children: Vec<AstHomTerm>,
1011 child_states: &mut Vec<String>,
1012 adjunction_states: &mut BTreeSet<String>,
1013) -> AstHomTerm {
1014 let ordinary = AstHomTerm::Symbol(format!("{}_{}", node.label, children.len()), children);
1015 if node.no_adjunction {
1016 ordinary
1017 } else {
1018 let state = state_name(&node.label, 'A');
1019 child_states.push(state.clone());
1020 adjunction_states.insert(state);
1021 AstHomTerm::Symbol(
1022 SUBSTITUTE.to_owned(),
1023 vec![AstHomTerm::Variable(child_states.len()), ordinary],
1024 )
1025 }
1026}
1027
1028#[derive(Clone, Debug)]
1029struct SortedTerm {
1030 term: AstHomTerm,
1031 sort: u8,
1032}
1033
1034fn string_term(
1035 tree_term: &AstHomTerm,
1036 child_states: &[String],
1037) -> Result<AstHomTerm, TulipacError> {
1038 fn convert(term: &AstHomTerm, child_states: &[String]) -> Result<SortedTerm, TulipacError> {
1039 match term {
1040 AstHomTerm::Variable(variable) => {
1041 let state = child_states.get(variable - 1).ok_or_else(|| {
1042 TulipacError::Semantic(format!("invalid variable ?{variable}"))
1043 })?;
1044 Ok(SortedTerm {
1045 term: term.clone(),
1046 sort: if state.ends_with("_S") { 1 } else { 2 },
1047 })
1048 }
1049 AstHomTerm::Symbol(label, children) if label == SUBSTITUTE => {
1050 let converted = children
1051 .iter()
1052 .map(|child| convert(child, child_states))
1053 .collect::<Result<Vec<_>, _>>()?;
1054 let operation = match (converted[0].sort, converted[1].sort) {
1055 (2, 1) => WRAP21,
1056 (2, 2) => WRAP22,
1057 sorts => {
1058 return Err(TulipacError::Semantic(format!(
1059 "invalid TAG wrap sorts {sorts:?}"
1060 )));
1061 }
1062 };
1063 Ok(SortedTerm {
1064 term: AstHomTerm::Symbol(
1065 operation.to_owned(),
1066 converted.into_iter().map(|term| term.term).collect(),
1067 ),
1068 sort: if operation == WRAP21 { 1 } else { 2 },
1069 })
1070 }
1071 AstHomTerm::Symbol(label, children) if label == HOLE => Ok(SortedTerm {
1072 term: AstHomTerm::Symbol(EE.to_owned(), Vec::new()),
1073 sort: 2,
1074 }),
1075 AstHomTerm::Symbol(label, children) => {
1076 let mut converted = children
1077 .iter()
1078 .map(|child| convert(child, child_states))
1079 .collect::<Result<Vec<_>, _>>()?;
1080 match converted.len() {
1081 0 => Ok(SortedTerm {
1082 term: AstHomTerm::Symbol(label.clone(), Vec::new()),
1083 sort: 1,
1084 }),
1085 1 => Ok(converted.remove(0)),
1086 _ => concatenate(&converted),
1087 }
1088 }
1089 }
1090 }
1091
1092 fn concatenate(children: &[SortedTerm]) -> Result<SortedTerm, TulipacError> {
1093 let left = children[0].clone();
1094 let right = if children.len() == 2 {
1095 children[1].clone()
1096 } else {
1097 concatenate(&children[1..])?
1098 };
1099 let (operation, sort) = match (left.sort, right.sort) {
1100 (1, 1) => (CONC11, 1),
1101 (1, 2) => (CONC12, 2),
1102 (2, 1) => (CONC21, 2),
1103 sorts => {
1104 return Err(TulipacError::Semantic(format!(
1105 "cannot concatenate TAG string sorts {sorts:?}"
1106 )));
1107 }
1108 };
1109 Ok(SortedTerm {
1110 term: AstHomTerm::Symbol(operation.to_owned(), vec![left.term, right.term]),
1111 sort,
1112 })
1113 }
1114
1115 Ok(convert(tree_term, child_states)?.term)
1116}
1117
1118fn feature_term(
1119 tree: &ElementaryTree,
1120 tree_term: &AstHomTerm,
1121 child_states: &[String],
1122 entry: &LexiconEntry,
1123) -> Result<AstHomTerm, TulipacError> {
1124 let mut node_ids = HashMap::new();
1125 let mut next_id = 1usize;
1126 for node in tree.arena.post_order(tree.root) {
1127 let id = if tree.arena.get_label(node).node_type == NodeType::Foot {
1128 "foot".to_owned()
1129 } else {
1130 let id = format!("n{next_id}");
1131 next_id += 1;
1132 id
1133 };
1134 node_ids.insert(node, id);
1135 }
1136
1137 let mut child_nodes = Vec::new();
1138 collect_feature_children(tree, tree.root, &node_ids, &mut child_nodes);
1139 if child_nodes.len() != child_states.len() {
1140 return Err(TulipacError::Semantic(
1141 "internal child ordering mismatch while building feature homomorphism".to_owned(),
1142 ));
1143 }
1144 let core = core_feature_literal(tree, &node_ids, entry)?;
1145
1146 fn convert(
1147 term: &AstHomTerm,
1148 child_states: &[String],
1149 child_nodes: &[String],
1150 core: &str,
1151 ) -> Result<AstHomTerm, TulipacError> {
1152 match term {
1153 AstHomTerm::Variable(variable) => {
1154 let index = variable - 1;
1155 let node = child_nodes.get(index).ok_or_else(|| {
1156 TulipacError::Semantic(format!("invalid feature variable ?{variable}"))
1157 })?;
1158 let state = &child_states[index];
1159 if state.ends_with("_S") {
1160 Ok(AstHomTerm::Symbol(
1161 format!("emb_{node}"),
1162 vec![AstHomTerm::Symbol(
1163 "proj_root".to_owned(),
1164 vec![term.clone()],
1165 )],
1166 ))
1167 } else {
1168 Ok(AstHomTerm::Symbol(
1169 format!("remap_root={node}t,foot={node}b"),
1170 vec![term.clone()],
1171 ))
1172 }
1173 }
1174 AstHomTerm::Symbol(label, children) if children.is_empty() => {
1175 if label == HOLE {
1176 Ok(AstHomTerm::Symbol("[]".to_owned(), Vec::new()))
1177 } else {
1178 Ok(AstHomTerm::Symbol(core.to_owned(), Vec::new()))
1179 }
1180 }
1181 AstHomTerm::Symbol(_, children) => {
1182 let converted = children
1183 .iter()
1184 .map(|child| convert(child, child_states, child_nodes, core))
1185 .collect::<Result<Vec<_>, _>>()?;
1186 let mut iter = converted.into_iter().rev();
1187 let mut result = iter.next().unwrap();
1188 for child in iter {
1189 result = AstHomTerm::Symbol("unify".to_owned(), vec![result, child]);
1190 }
1191 Ok(result)
1192 }
1193 }
1194 }
1195
1196 convert(tree_term, child_states, &child_nodes, &core)
1197}
1198
1199fn collect_feature_children(
1200 tree: &ElementaryTree,
1201 node: Tree,
1202 node_ids: &HashMap<Tree, String>,
1203 out: &mut Vec<String>,
1204) {
1205 for &child in tree.arena.get_children(node) {
1206 collect_feature_children(tree, child, node_ids, out);
1207 }
1208 let data = tree.arena.get_label(node);
1209 match data.node_type {
1210 NodeType::Substitution => out.push(node_ids[&node].clone()),
1211 NodeType::Foot => {}
1212 NodeType::Default | NodeType::Head if !data.no_adjunction => {
1213 out.push(node_ids[&node].clone())
1214 }
1215 NodeType::Default | NodeType::Head => {}
1216 }
1217}
1218
1219fn core_feature_literal(
1220 tree: &ElementaryTree,
1221 node_ids: &HashMap<Tree, String>,
1222 entry: &LexiconEntry,
1223) -> Result<String, TulipacError> {
1224 let mut attributes = Vec::<(String, FeatureMap)>::new();
1225 let mut root_top = None;
1226
1227 for node in tree.arena.post_order(tree.root) {
1228 let data = tree.arena.get_label(node);
1229 let id = &node_ids[&node];
1230 match data.node_type {
1231 NodeType::Foot => {
1232 attributes.push((id.clone(), data.top.clone().unwrap_or_default()));
1233 }
1234 NodeType::Substitution => {
1235 attributes.push((id.clone(), data.top.clone().unwrap_or_default()));
1236 }
1237 NodeType::Default | NodeType::Head => {
1238 let top_name = format!("{id}t");
1239 let bottom_name = format!("{id}b");
1240 let bottom = if data.node_type == NodeType::Head {
1241 data.bottom
1242 .clone()
1243 .unwrap_or_default()
1244 .merge(&entry.features)?
1245 } else {
1246 data.bottom.clone().unwrap_or_default()
1247 };
1248 attributes.push((top_name.clone(), data.top.clone().unwrap_or_default()));
1249 attributes.push((bottom_name, bottom));
1250 if node == tree.root {
1251 root_top = Some(top_name);
1252 }
1253 }
1254 }
1255 }
1256
1257 let root_top = root_top.ok_or_else(|| {
1258 TulipacError::Semantic(
1259 "elementary-tree root cannot be a foot or substitution node".to_owned(),
1260 )
1261 })?;
1262 let mut defined = HashSet::new();
1263 let mut fields = Vec::new();
1264 for (attribute, features) in attributes {
1265 let mut value = feature_map_literal(&features, &mut defined);
1266 if attribute == root_top {
1267 value = format!("#__root {value}");
1268 defined.insert("__root".to_owned());
1269 }
1270 fields.push(format!("{attribute}: {value}"));
1271 }
1272 fields.push("root: #__root".to_owned());
1273 Ok(format!("[{}]", fields.join(", ")))
1274}
1275
1276fn feature_map_literal(features: &FeatureMap, defined: &mut HashSet<String>) -> String {
1277 let fields = features
1278 .0
1279 .iter()
1280 .map(|(attribute, value)| {
1281 let value = match value {
1282 FeatureAtom::Constant(value) => value.clone(),
1283 FeatureAtom::Variable(variable) => {
1284 defined.insert(variable.clone());
1285 format!("#{variable}")
1286 }
1287 };
1288 format!("{attribute}: {value}")
1289 })
1290 .collect::<Vec<_>>();
1291 format!("[{}]", fields.join(", "))
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296 use super::*;
1297 use crate::{Binarizing, TagStringAlgebra, TagTreeAlgebra};
1298
1299 const CHASING: &str = r#"
1300 tree trans:
1301 S {
1302 NP![case=nom][]
1303 VP {
1304 V+
1305 NP![case=acc][]
1306 }
1307 }
1308
1309 tree np_n:
1310 NP[][case=?case] {
1311 Det! [case=?case][]
1312 N+ [case=?case][]
1313 }
1314
1315 tree det:
1316 Det+
1317
1318 word 'jagt': trans
1319 word 'hund': np_n[case=nom]
1320 word 'hasen': np_n[case=acc]
1321 word 'der': det[case=nom]
1322 word 'den': det[case=acc]
1323 "#;
1324
1325 const ADJUNCTION: &str = r#"
1326 tree clause:
1327 S @NA {
1328 NP!
1329 VP {
1330 V+ @NA
1331 }
1332 }
1333
1334 tree noun:
1335 NP @NA {
1336 N+ @NA
1337 }
1338
1339 tree adverb:
1340 VP @NA {
1341 Adv+ @NA
1342 VP*
1343 }
1344
1345 word 'john': noun
1346 word 'sleeps': clause
1347 word 'quickly': adverb
1348 "#;
1349
1350 const RECURSIVE_ADJUNCTION: &str = r#"
1351 tree seed:
1352 S @NA {
1353 VP {
1354 Center+ @NA
1355 }
1356 }
1357
1358 tree copy:
1359 VP {
1360 Left+ @NA
1361 VP*
1362 Right+ @NA
1363 }
1364
1365 word 'c': seed
1366 word 'a': copy
1367 "#;
1368
1369 const SHIEBER: &str = r#"
1370 family vinf_tv: { vinf_tv, vinf_tv_aux }
1371 tree vinf_tv:
1372 S @NA {
1373 np! [case=nom][]
1374 S { np! [case=?o] [] }
1375 v+ [objcase=?o] []
1376 }
1377 tree vinf_tv_aux:
1378 S @NA {
1379 S { S @NA { np! [case=?o] [] S* } }
1380 v+ [objcase=?o][]
1381 }
1382 family np_n: { np_n }
1383 tree np_n:
1384 np [] [case=?c] { n+ [case=?c] [] }
1385 tree adj_det:
1386 np [] [case=?c] {
1387 det+ [case=?c] []
1388 np* [case=?c] []
1389 }
1390 word 'de': adj_det[case=acc]
1391 word 'huus': np_n
1392 word 'aastriiche': <vinf_tv>[objcase=acc]
1393 word 'laa': <vinf_tv>[objcase=acc]
1394 "#;
1395
1396 fn best_derivation(irtg: &Irtg, sentence: &str) -> String {
1397 let string = irtg.interpretation::<TagStringAlgebra>("string").unwrap();
1398 let value = string.parse_object(sentence).unwrap();
1399 let best = irtg
1400 .parse([string.input(value)])
1401 .unwrap()
1402 .automaton
1403 .viterbi()
1404 .unwrap();
1405 irtg.resolve_derivation(best.arena(), best.root())
1406 .to_string()
1407 }
1408
1409 #[test]
1410 fn reads_and_parses_tulipac_grammar() {
1411 let irtg = TulipacInputCodec.decode(CHASING).unwrap();
1412 let string = irtg.interpretation::<TagStringAlgebra>("string").unwrap();
1413 let value = string.parse_object("der hund jagt den hasen").unwrap();
1414 assert!(
1415 irtg.parse([string.input(value)])
1416 .unwrap()
1417 .automaton
1418 .viterbi()
1419 .is_some()
1420 );
1421 assert!(irtg.interpretation::<TagTreeAlgebra>("tree").is_ok());
1422 }
1423
1424 #[test]
1425 fn parses_tulipac_adjunction_with_exact_derivations() {
1426 let irtg = TulipacInputCodec.decode(ADJUNCTION).unwrap();
1427
1428 assert_eq!(
1429 best_derivation(&irtg, "john sleeps"),
1430 "clause-sleeps(noun-john, '*NOP*_VP_A')"
1431 );
1432 assert_eq!(
1433 best_derivation(&irtg, "john quickly sleeps"),
1434 "clause-sleeps(noun-john, adverb-quickly)"
1435 );
1436 }
1437
1438 #[test]
1439 fn recursively_adjoins_an_auxiliary_tree_around_its_foot() {
1440 let irtg = TulipacInputCodec.decode(RECURSIVE_ADJUNCTION).unwrap();
1441
1442 assert_eq!(
1443 best_derivation(&irtg, "a a c a a"),
1444 "seed-c(copy-a(copy-a('*NOP*_VP_A')))"
1445 );
1446 }
1447
1448 #[test]
1449 fn supports_families_lemmas_and_no_adjunction() {
1450 let irtg = TulipacInputCodec
1451 .decode(
1452 r#"
1453 family verbs: { v }
1454 tree v: S @NA { V+ }
1455 lemma 'sleep': <verbs> [tense=pres] {
1456 word 'sleeps'
1457 }
1458 "#,
1459 )
1460 .unwrap();
1461 let string = irtg.interpretation::<TagStringAlgebra>("string").unwrap();
1462 let value = string.parse_object("sleeps").unwrap();
1463 assert!(
1464 irtg.parse([string.input(value)])
1465 .unwrap()
1466 .automaton
1467 .viterbi()
1468 .is_some()
1469 );
1470 }
1471
1472 #[test]
1473 fn rejects_unknown_tree_and_extra_closing_brace() {
1474 assert!(TulipacInputCodec.decode("word x: missing").is_err());
1475 assert!(
1476 TulipacInputCodec
1477 .decode("tree t: S { V+ } } word x: t")
1478 .is_err()
1479 );
1480 }
1481
1482 #[test]
1483 fn generated_tree_interpretation_is_not_binarized() {
1484 let irtg = TulipacInputCodec.decode(CHASING).unwrap();
1485 assert!(irtg.interpretation::<TagTreeAlgebra>("tree").is_ok());
1486 assert!(
1487 irtg.interpretation::<Binarizing<TagTreeAlgebra>>("tree")
1488 .is_err()
1489 );
1490 }
1491
1492 #[test]
1493 fn feature_filter_enforces_tulipac_agreement() {
1494 let irtg = TulipacInputCodec
1495 .decode(
1496 r#"
1497 tree noun:
1498 S {
1499 Det! [gen=?g]
1500 N+ [gen=?g]
1501 }
1502 tree det:
1503 Det+
1504 word 'Hund': noun[gen=masc]
1505 word 'der': det[gen=masc]
1506 word 'die': det[gen=fem]
1507 "#,
1508 )
1509 .unwrap();
1510 let string = irtg.interpretation::<TagStringAlgebra>("string").unwrap();
1511
1512 let good = string.parse_object("der Hund").unwrap();
1513 let good_chart = irtg.parse([string.input(good)]).unwrap();
1514 assert!(
1515 irtg.filter_non_null(&good_chart.automaton, "ft")
1516 .unwrap()
1517 .viterbi()
1518 .is_some()
1519 );
1520
1521 let bad = string.parse_object("die Hund").unwrap();
1522 let bad_chart = irtg.parse([string.input(bad)]).unwrap();
1523 assert!(bad_chart.automaton.viterbi().is_some());
1524 assert!(
1525 irtg.filter_non_null(&bad_chart.automaton, "ft")
1526 .unwrap()
1527 .viterbi()
1528 .is_none()
1529 );
1530 }
1531
1532 #[test]
1533 fn shieber_subject_adjunction_clashes_and_feature_filter_removes_it() {
1534 let irtg = TulipacInputCodec.decode(SHIEBER).unwrap();
1535 let mut language = irtg.grammar().sorted_language();
1536 let mut successful = 0;
1537 let mut failed = 0;
1538 for _ in 0..12 {
1539 let weighted = language.next().unwrap();
1540 let (arena, root) = language.clone_tree(weighted.tree());
1541 if irtg
1542 .interpretation_ref("ft")
1543 .unwrap()
1544 .evaluate_derivation(&arena, root)
1545 .is_ok()
1546 {
1547 successful += 1;
1548 } else {
1549 failed += 1;
1550 }
1551 }
1552 assert!(successful > 0);
1553 assert!(failed > 0);
1554
1555 let filtered = irtg.filter_non_null(irtg.grammar(), "ft").unwrap();
1556 let mut filtered_language = filtered.sorted_language();
1557 for _ in 0..3 {
1558 let weighted = filtered_language.next().unwrap();
1559 let (arena, root) = filtered_language.clone_tree(weighted.tree());
1560 assert!(
1561 irtg.interpretation_ref("ft")
1562 .unwrap()
1563 .evaluate_derivation(&arena, root)
1564 .is_ok()
1565 );
1566 }
1567 }
1568
1569 #[test]
1570 fn read_path_resolves_relative_includes() {
1571 let directory =
1572 std::env::temp_dir().join(format!("rusty_alto_tulipac_{}", std::process::id()));
1573 std::fs::create_dir_all(&directory).unwrap();
1574 let trees = directory.join("trees.tag");
1575 let grammar = directory.join("grammar.tag");
1576 std::fs::write(&trees, "tree v: S @NA { V+ }").unwrap();
1577 std::fs::write(&grammar, "#include 'trees.tag'\nword sleeps: v").unwrap();
1578
1579 let stream_error = TulipacInputCodec
1580 .decode("#include 'trees.tag'\nword sleeps: v")
1581 .unwrap_err();
1582 assert!(stream_error.to_string().contains("requires"));
1583
1584 let registry = crate::InputCodecRegistry::standard();
1585 let codec = registry.codec_for_path::<Irtg>(&grammar).unwrap();
1586 let irtg = codec.read_path(&grammar).unwrap();
1587 let string = irtg.interpretation::<TagStringAlgebra>("string").unwrap();
1588 let value = string.parse_object("sleeps").unwrap();
1589 assert!(
1590 irtg.parse([string.input(value)])
1591 .unwrap()
1592 .automaton
1593 .viterbi()
1594 .is_some()
1595 );
1596
1597 std::fs::remove_dir_all(directory).unwrap();
1598 }
1599}