1pub mod corpus;
25pub mod reserved;
26
27pub use corpus::{CompoundEntry, CorpusTier, PredicateEntry, Swap};
28
29use std::collections::BTreeMap;
30use std::sync::LazyLock;
31
32pub fn alias(name: &str) -> Option<&'static PredicateEntry> {
36 corpus::corpus_predicate(name)
37}
38
39pub fn compound(spelling: &str) -> Option<&'static CompoundEntry> {
41 corpus::corpus_compound(spelling)
42}
43
44pub fn compound_by_relation(rel: &str) -> Option<&'static CompoundEntry> {
48 corpus::corpus_compounds().find(|c| c.relation == rel)
49}
50
51pub fn relation_places(rel: &str) -> Option<&'static [&'static str]> {
55 alias(rel)
56 .map(|e| e.places)
57 .or_else(|| compound_by_relation(rel).map(|c| c.places))
58}
59
60pub fn by_provenance(gismu: &str) -> Option<&'static PredicateEntry> {
64 static BY_PROVENANCE: LazyLock<BTreeMap<&'static str, &'static PredicateEntry>> =
65 LazyLock::new(|| {
66 corpus::corpus_entries()
67 .filter(|e| e.swap.is_none())
68 .map(|e| (e.source_gismu, e))
69 .collect()
70 });
71 BY_PROVENANCE.get(gismu).copied()
72}
73
74pub fn canonical_alias(gismu: &str) -> Option<&'static str> {
77 by_provenance(gismu).map(|e| e.name)
78}
79
80pub fn get_arity(word: &str) -> Option<usize> {
82 alias(word)
83 .map(|e| e.arity() as usize)
84 .or_else(|| compound_by_relation(word).map(|c| c.arity() as usize))
85}
86
87pub fn get_gloss(word: &str) -> Option<&'static str> {
89 alias(word)
90 .map(|e| e.gloss)
91 .or_else(|| compound_by_relation(word).map(|c| c.gloss))
92}
93
94pub fn get_template(word: &str) -> Option<&'static str> {
98 alias(word)
99 .map(|e| e.template)
100 .or_else(|| compound_by_relation(word).map(|c| c.template))
101 .flatten()
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn english_lookups() {
110 let goes = alias("goes").expect("goes");
111 assert_eq!(goes.arity(), 5);
112 assert_eq!(goes.source_gismu, "klama");
113 assert_eq!(get_arity("goes"), Some(5));
114 assert_eq!(get_arity("dog"), Some(2));
115 assert_eq!(get_gloss("dog"), Some("dog"));
116 assert_eq!(get_template("dog"), Some("{x1} is a dog"));
117 assert_eq!(get_arity("quantity"), Some(3));
118 }
119
120 #[test]
121 fn converted_entries_carry_their_base() {
122 let owned = alias("owned").expect("owned");
123 let swap = owned.swap.expect("owned is converted");
124 assert_eq!(swap.with, 2);
125 assert_eq!(swap.base, "owns");
126 assert_eq!(owned.source_gismu, "ponse");
127 assert_eq!(canonical_alias("ponse"), Some("owns"));
129 }
130
131 #[test]
132 fn provenance_bridge() {
133 assert_eq!(by_provenance("klama").map(|e| e.name), Some("goes"));
134 assert_eq!(by_provenance("gerku").map(|e| e.name), Some("dog"));
135 assert!(by_provenance("nosuchword").is_none());
136 }
137
138 #[test]
139 fn gismu_never_resolve() {
140 assert_eq!(get_arity("klama"), None);
142 assert_eq!(get_template("gerku"), None);
143 assert!(alias("klama").is_none());
144 assert_eq!(by_provenance("klama").map(|e| e.name), Some("goes"));
146 }
147
148 #[test]
149 fn compounds_resolve_by_plus_spelling_only() {
150 let cu = compound("computer+user").expect("seed compound");
151 assert_eq!(cu.relation, "computer_user");
152 assert_eq!(cu.arity(), 3);
153 assert!(
154 compound("dog+cat").is_none(),
155 "undefined compounds fail closed"
156 );
157 assert!(
158 alias("computer_user").is_none(),
159 "the relation ident is not an input spelling"
160 );
161 }
162
163 #[test]
164 fn compound_relations_resolve_post_emit() {
165 assert_eq!(
168 compound_by_relation("computer_user").map(|c| c.name),
169 Some("computer+user")
170 );
171 assert_eq!(get_arity("computer_user"), Some(3));
172 assert!(get_gloss("computer_user").is_some());
173 assert_eq!(relation_places("computer_user").map(|p| p[0]), Some("user"));
174 assert_eq!(relation_places("dog").map(|p| p[0]), Some("dog"));
175 assert!(relation_places("no_such_relation").is_none());
176 }
177
178 #[test]
179 fn no_shipped_name_is_reserved() {
180 for e in corpus::corpus_entries() {
181 assert!(
182 !reserved::is_reserved(e.name),
183 "corpus name {:?} collides with a nibli KR keyword",
184 e.name
185 );
186 }
187 }
188
189 #[test]
190 fn every_place_is_named() {
191 for e in corpus::corpus_entries() {
193 assert!(!e.places.is_empty() && e.places.len() <= 5, "{:?}", e.name);
194 for p in e.places {
195 assert!(!p.is_empty(), "{:?} has an unnamed place", e.name);
196 }
197 }
198 }
199}