1use iri_rs::IriBuf;
2
3use super::{BindingRef, Context, Key};
4use crate::{Container, Direction, HashMap, LenientLangTag, LenientLangTagBuf, Nullable, Term, Type};
5use hashbrown::hash_map::Entry;
6use std::{fmt, hash::Hash};
7
8#[inline]
12fn term_lt(a: &Key, b: &Key) -> bool {
13 let al = a.as_str().len();
14 let bl = b.as_str().len();
15 al < bl || (al == bl && a.as_str() < b.as_str())
16}
17
18#[inline]
19fn keep_smaller_opt(slot: &mut Option<Key>, candidate: &Key) {
20 match slot {
21 None => *slot = Some(*candidate),
22 Some(existing) if term_lt(candidate, existing) => *slot = Some(*candidate),
23 _ => {}
24 }
25}
26
27#[inline]
28fn keep_smaller(slot: &mut Key, candidate: &Key) {
29 if term_lt(candidate, slot) {
30 *slot = *candidate;
31 }
32}
33
34#[derive(Clone, PartialEq, Eq)]
35pub enum TypeSelection<T = IriBuf> {
37 Reverse,
39 Any,
41 Type(Type<T>),
43}
44
45impl<T: fmt::Debug> fmt::Debug for TypeSelection<T> {
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 match self {
48 TypeSelection::Reverse => write!(f, "Reverse"),
49 TypeSelection::Any => write!(f, "Any"),
50 TypeSelection::Type(ty) => write!(f, "Type({ty:?})"),
51 }
52 }
53}
54
55struct InverseType<T> {
56 reverse: Option<Key>,
57 any: Option<Key>,
58 map: HashMap<Type<T>, Key>,
59}
60
61impl<T> InverseType<T> {
62 fn select(&self, selection: &TypeSelection<T>) -> Option<&Key>
63 where
64 T: Hash + Eq,
65 {
66 match selection {
67 TypeSelection::Reverse => self.reverse.as_ref(),
68 TypeSelection::Any => self.any.as_ref(),
69 TypeSelection::Type(ty) => self.map.get(ty),
70 }
71 }
72
73 fn set_any(&mut self, term: &Key) {
74 keep_smaller_opt(&mut self.any, term);
75 }
76
77 fn set_none(&mut self, term: &Key)
78 where
79 T: Clone + Hash + Eq,
80 {
81 self.set(&Type::None, term);
82 }
83
84 fn set(&mut self, ty: &Type<T>, term: &Key)
85 where
86 T: Clone + Hash + Eq,
87 {
88 match self.map.entry(ty.clone()) {
89 Entry::Vacant(v) => {
90 v.insert(*term);
91 }
92 Entry::Occupied(mut o) if term_lt(term, o.get()) => {
93 o.insert(*term);
94 }
95 Entry::Occupied(_) => {}
96 }
97 }
98}
99
100type LangDir = Nullable<(Option<LenientLangTagBuf>, Option<Direction>)>;
101
102struct LangDirRef<'a>(Nullable<(Option<&'a LenientLangTag>, Option<Direction>)>);
109
110impl Hash for LangDirRef<'_> {
111 #[inline]
112 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
113 self.0.hash(state);
114 }
115}
116
117impl hashbrown::Equivalent<LangDir> for LangDirRef<'_> {
118 #[inline]
119 fn equivalent(&self, key: &LangDir) -> bool {
120 match (&self.0, key) {
121 (Nullable::Null, Nullable::Null) => true,
122 (Nullable::Some((lang, dir)), Nullable::Some((key_lang, key_dir))) => {
123 dir == key_dir
124 && match (lang, key_lang) {
125 (None, None) => true,
126 (Some(lang), Some(key_lang)) => *lang == &**key_lang,
127 _ => false,
128 }
129 }
130 _ => false,
131 }
132 }
133}
134
135struct InverseLang {
136 any: Option<Key>,
137 map: HashMap<LangDir, Key>,
138}
139
140#[derive(Clone, Copy, PartialEq, Eq, Debug)]
141pub enum LangSelection<'a> {
143 Any,
145 Lang(Nullable<(Option<&'a LenientLangTag>, Option<Direction>)>),
147}
148
149impl InverseLang {
150 fn select(&self, selection: LangSelection) -> Option<&Key> {
151 match selection {
152 LangSelection::Any => self.any.as_ref(),
153 LangSelection::Lang(lang_dir) => self.map.get(&LangDirRef(lang_dir)),
154 }
155 }
156
157 fn set_any(&mut self, term: &Key) {
158 keep_smaller_opt(&mut self.any, term);
159 }
160
161 fn set_none(&mut self, term: &Key) {
162 self.set(Nullable::Some((None, None)), term);
163 }
164
165 fn set(&mut self, lang_dir: Nullable<(Option<&LenientLangTag>, Option<Direction>)>, term: &Key) {
166 let lang_dir = lang_dir.map(|(l, d)| (l.map(std::borrow::ToOwned::to_owned), d));
167 match self.map.entry(lang_dir) {
168 Entry::Vacant(v) => {
169 v.insert(*term);
170 }
171 Entry::Occupied(mut o) if term_lt(term, o.get()) => {
172 o.insert(*term);
173 }
174 Entry::Occupied(_) => {}
175 }
176 }
177}
178
179struct InverseContainer<T> {
180 language: InverseLang,
181 typ: InverseType<T>,
182 any: Any,
183}
184
185struct Any {
186 none: Key,
187}
188
189impl<T> InverseContainer<T> {
190 pub fn new(term: &Key) -> InverseContainer<T> {
191 InverseContainer {
192 language: InverseLang {
193 any: None,
194 map: HashMap::default(),
195 },
196 typ: InverseType {
197 reverse: None,
198 any: None,
199 map: HashMap::default(),
200 },
201 any: Any { none: *term },
202 }
203 }
204}
205
206pub struct InverseDefinition<T> {
208 map: HashMap<Container, InverseContainer<T>>,
209}
210
211impl<T> InverseDefinition<T> {
212 fn new() -> InverseDefinition<T> {
213 InverseDefinition { map: HashMap::default() }
214 }
215
216 fn get(&self, container: Container) -> Option<&InverseContainer<T>> {
217 self.map.get(&container)
218 }
219
220 fn reference_mut<F: FnOnce() -> InverseContainer<T>>(&mut self, container: Container, insert: F) -> &mut InverseContainer<T> {
221 self.map.entry(container).or_insert_with(insert)
222 }
223
224 #[must_use]
228 pub fn select(&self, containers: &[Container], selection: &Selection<T>) -> Option<&Key>
229 where
230 T: Clone + Hash + Eq,
231 {
232 for container in containers {
233 if let Some(type_lang_map) = self.get(*container) {
234 match selection {
235 Selection::Any => return Some(&type_lang_map.any.none),
236 Selection::Type(preferred_values) => {
237 for item in preferred_values {
238 if let Some(term) = type_lang_map.typ.select(item) {
239 return Some(term);
240 }
241 }
242 }
243 Selection::Lang(preferred_values) => {
244 for item in preferred_values {
245 if let Some(term) = type_lang_map.language.select(*item) {
246 return Some(term);
247 }
248 }
249 }
250 }
251 }
252 }
253
254 None
255 }
256}
257
258pub struct InverseContext<T, B> {
260 map: HashMap<Term<T, B>, InverseDefinition<T>>,
261}
262
263#[derive(PartialEq)]
265pub enum Selection<'a, T> {
266 Any,
268 Type(Vec<TypeSelection<T>>),
270 Lang(Vec<LangSelection<'a>>),
272}
273
274impl<T: fmt::Debug> fmt::Debug for Selection<'_, T> {
275 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
276 match self {
277 Selection::Any => write!(f, "Any"),
278 Selection::Type(s) => write!(f, "Type({s:?})"),
279 Selection::Lang(s) => write!(f, "Lang({s:?})"),
280 }
281 }
282}
283
284impl<T, B> InverseContext<T, B> {
285 #[must_use]
287 pub fn new() -> Self {
288 InverseContext { map: HashMap::default() }
289 }
290}
291
292impl<T: Hash + Eq, B: Hash + Eq> InverseContext<T, B> {
293 pub fn contains(&self, term: &Term<T, B>) -> bool {
295 self.map.contains_key(term)
296 }
297
298 pub fn insert(&mut self, term: Term<T, B>, value: InverseDefinition<T>) {
301 self.map.insert(term, value);
302 }
303
304 pub fn get(&self, term: &Term<T, B>) -> Option<&InverseDefinition<T>> {
306 self.map.get(term)
307 }
308
309 pub fn get_mut(&mut self, term: &Term<T, B>) -> Option<&mut InverseDefinition<T>> {
311 self.map.get_mut(term)
312 }
313
314 fn reference_mut<F: FnOnce() -> InverseDefinition<T>>(&mut self, term: &Term<T, B>, insert: F) -> &mut InverseDefinition<T>
315 where
316 T: Clone,
317 B: Clone,
318 {
319 self.map.entry(term.clone()).or_insert_with(insert)
320 }
321
322 pub fn select(&self, var: &Term<T, B>, containers: &[Container], selection: &Selection<T>) -> Option<&Key>
327 where
328 T: Clone,
329 {
330 match self.get(var) {
331 Some(container_map) => container_map.select(containers, selection),
332 None => None,
333 }
334 }
335}
336
337impl<T, B> Default for InverseContext<T, B> {
338 fn default() -> Self {
339 Self::new()
340 }
341}
342
343impl<'a, T: Clone + Hash + Eq, B: Clone + Hash + Eq> From<&'a Context<T, B>> for InverseContext<T, B> {
344 fn from(context: &'a Context<T, B>) -> Self {
345 let mut result = InverseContext::new();
346
347 for binding in context.definitions() {
351 if let BindingRef::Normal(term, term_definition) = binding
352 && let Some(var) = term_definition.value.as_ref()
353 {
354 let container = &term_definition.container;
355 let container_map = result.reference_mut(var, InverseDefinition::new);
356 let type_lang_map = container_map.reference_mut(*container, || InverseContainer::new(term));
357
358 keep_smaller(&mut type_lang_map.any.none, term);
362
363 let type_map = &mut type_lang_map.typ;
364 let lang_map = &mut type_lang_map.language;
365
366 if term_definition.reverse_property {
367 keep_smaller_opt(&mut type_map.reverse, term);
369 } else {
370 match &term_definition.typ {
371 Some(Type::None) => {
372 type_map.set_any(term);
374 lang_map.set_any(term);
375 }
376 Some(typ) => {
377 type_map.set(typ, term);
379 }
380 None => {
381 match (&term_definition.language, &term_definition.direction) {
382 (Some(language), Some(direction)) => {
383 match (language, direction) {
386 (Nullable::Some(language), Nullable::Some(direction)) => {
387 lang_map.set(Nullable::Some((Some(language.as_lenient_lang_tag_ref()), Some(*direction))), term);
388 }
389 (Nullable::Some(language), Nullable::Null) => {
390 lang_map.set(Nullable::Some((Some(language.as_lenient_lang_tag_ref()), None)), term);
391 }
392 (Nullable::Null, Nullable::Some(direction)) => lang_map.set(Nullable::Some((None, Some(*direction))), term),
393 (Nullable::Null, Nullable::Null) => lang_map.set(Nullable::Null, term),
394 }
395 }
396 (Some(language), None) => {
397 match language {
400 Nullable::Some(language) => lang_map.set(Nullable::Some((Some(language.as_lenient_lang_tag_ref()), None)), term),
401 Nullable::Null => lang_map.set(Nullable::Null, term),
402 }
403 }
404 (None, Some(direction)) => {
405 match direction {
408 Nullable::Some(direction) => lang_map.set(Nullable::Some((None, Some(*direction))), term),
409 Nullable::Null => lang_map.set(Nullable::Some((None, None)), term),
410 }
411 }
412 (None, None) => {
413 lang_map.set(Nullable::Some((context.default_language(), context.default_base_direction())), term);
414 lang_map.set_none(term);
415 type_map.set_none(term);
416 }
417 }
418 }
419 }
420 }
421 }
422 }
423
424 result
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::HashMap;
432
433 #[test]
439 fn lang_dir_ref_hashes_like_the_owned_key() {
440 let (owned_tag, _) = LenientLangTagBuf::new("en-GB".to_string());
441 let (borrowed_tag, _) = LenientLangTag::new("en-GB");
442
443 let cases: Vec<(LangDir, LangDirRef)> = vec![
444 (Nullable::Null, LangDirRef(Nullable::Null)),
445 (Nullable::Some((None, None)), LangDirRef(Nullable::Some((None, None)))),
446 (
447 Nullable::Some((None, Some(Direction::Rtl))),
448 LangDirRef(Nullable::Some((None, Some(Direction::Rtl)))),
449 ),
450 (
451 Nullable::Some((Some(owned_tag.clone()), None)),
452 LangDirRef(Nullable::Some((Some(borrowed_tag), None))),
453 ),
454 (
455 Nullable::Some((Some(owned_tag), Some(Direction::Ltr))),
456 LangDirRef(Nullable::Some((Some(borrowed_tag), Some(Direction::Ltr)))),
457 ),
458 ];
459
460 for (owned, borrowed) in cases {
461 let mut map: HashMap<LangDir, u32> = HashMap::default();
462 map.insert(owned.clone(), 42);
463 assert_eq!(map.get(&borrowed), Some(&42), "borrowed lookup missed {owned:?}");
464 }
465 }
466
467 #[test]
470 fn lang_dir_ref_still_distinguishes_keys() {
471 let (owned_tag, _) = LenientLangTagBuf::new("en".to_string());
472 let (other_tag, _) = LenientLangTag::new("fr");
473
474 let mut map: HashMap<LangDir, u32> = HashMap::default();
475 map.insert(Nullable::Some((Some(owned_tag), None)), 42);
476
477 assert_eq!(map.get(&LangDirRef(Nullable::Some((Some(other_tag), None)))), None);
478 assert_eq!(map.get(&LangDirRef(Nullable::Null)), None);
479 }
480}