Skip to main content

rusty_alto/codecs/
tulipac.rs

1//! Input codec for Alto's Tulipac TAG grammar format.
2//!
3//! The codec parses elementary trees, tree families, words, lemmas, feature
4//! annotations, node markers, comments, and `#include` directives. It compiles
5//! them into an [`Irtg`] with `string` and `tree` interpretations and, when
6//! feature annotations occur, an `ft` feature-structure interpretation.
7
8use 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/// Alto-compatible reader for Tulipac `.tag` grammars.
37#[derive(Clone, Debug, Default)]
38pub struct TulipacInputCodec;
39
40impl TulipacInputCodec {
41    /// Construct a stateless Tulipac codec.
42    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/// Failure while reading, parsing, validating, or compiling a Tulipac grammar.
85#[derive(Debug)]
86pub enum TulipacError {
87    /// A grammar or included file could not be read.
88    Io {
89        /// Path that could not be read.
90        path: PathBuf,
91        /// Underlying filesystem error.
92        source: std::io::Error,
93    },
94    /// Tokenization failed.
95    Lex {
96        /// UTF-8 byte offset of the offending input.
97        offset: usize,
98        /// Human-readable diagnostic.
99        message: String,
100    },
101    /// The token stream did not match the Tulipac grammar.
102    Parse {
103        /// UTF-8 byte offset of the offending token.
104        offset: usize,
105        /// Human-readable diagnostic.
106        message: String,
107    },
108    /// Declarations were syntactically valid but inconsistent.
109    Semantic(String),
110    /// Conversion to rusty-alto's IRTG representation failed.
111    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}