1use std::borrow::Cow;
5use std::str::FromStr;
6use std::sync::LazyLock;
7
8#[cfg(feature = "ck3")]
9use tiger_tables::ck3::misc::CUSTOM_RELIGION_LOCAS;
10pub use tiger_tables::datatype::Datatype;
11use tiger_tables::datatype::*;
12
13use crate::context::ScopeContext;
14#[cfg(feature = "jomini")]
15use crate::data::customloca::CustomLocalization;
16use crate::data::localization::Language;
17#[cfg(feature = "jomini")]
18use crate::data::scripted_guis::ScriptedGui;
19use crate::datacontext::DataContext;
20use crate::everything::Everything;
21use crate::game::Game;
22use crate::helpers::BiTigerHashMap;
23#[cfg(feature = "hoi4")]
24use crate::helpers::is_country_tag;
25#[cfg(feature = "hoi4")]
26use crate::hoi4::data::scripted_localisation::ScriptedLocalisation;
27use crate::item::Item;
28#[cfg(feature = "hoi4")]
29use crate::report::Severity;
30#[cfg(feature = "jomini")]
31use crate::report::err;
32use crate::report::{ErrorKey, warn};
33use crate::scopes::Scopes;
34use crate::token::Token;
35
36#[derive(Clone, Debug, Default)]
50pub struct CodeChain {
51 pub codes: Box<[Code]>,
52}
53
54#[derive(Clone, Debug)]
58pub struct Code {
59 pub name: Token,
60 pub arguments: Vec<CodeArg>,
61}
62
63#[derive(Clone, Debug)]
65#[allow(dead_code)] pub enum CodeArg {
67 Chain(CodeChain),
69 Literal(Token),
73}
74
75impl CodeChain {
76 #[cfg(feature = "ck3")]
77 pub fn as_gameconcept(&self) -> Option<&Token> {
78 if self.codes.len() == 1 && self.codes[0].arguments.is_empty() {
79 Some(&self.codes[0].name)
80 } else if self.codes.len() == 1
81 && self.codes[0].name.is("Concept")
82 && self.codes[0].arguments.len() == 2
83 {
84 if let CodeArg::Literal(token) = &self.codes[0].arguments[0] {
85 Some(token)
86 } else {
87 None
88 }
89 } else {
90 None
91 }
92 }
93
94 #[cfg(feature = "jomini")]
95 pub fn without_last(&self) -> Self {
96 if self.codes.is_empty() {
97 CodeChain { codes: Box::new([]) }
98 } else {
99 CodeChain { codes: Box::from(&self.codes[..self.codes.len() - 1]) }
100 }
101 }
102}
103
104#[derive(Copy, Clone, Debug)]
106enum LookupResult {
107 NotFound,
109 WrongType,
111 Found(Args, Datatype),
114}
115
116#[cfg(feature = "jomini")]
124fn validate_custom(token: &Token, data: &Everything, scopes: Scopes, lang: Option<Language>) {
125 data.verify_exists(Item::CustomLocalization, token);
126 if let Some((key, block)) = data.get_key_block(Item::CustomLocalization, token.as_str()) {
127 CustomLocalization::validate_custom_call(key, block, data, token, scopes, lang, "", None);
128 }
129}
130
131#[cfg(feature = "jomini")]
140fn validate_argument(
141 arg: &CodeArg,
142 data: &Everything,
143 sc: &mut ScopeContext,
144 dc: &DataContext,
145 expect_arg: Arg,
146 lang: Option<Language>,
147 format: Option<&Token>,
148) {
149 match expect_arg {
150 Arg::DType(expect_type) => {
151 match arg {
152 CodeArg::Chain(chain) => {
153 validate_datatypes(chain, data, sc, dc, expect_type, lang, format, false);
154 }
155 CodeArg::Literal(token) => {
156 if token.as_str().starts_with('(') && token.as_str().contains(')') {
157 let dtype =
159 token.as_str().split(')').next().unwrap().strip_prefix('(').unwrap();
160 if dtype == "hex" {
161 if expect_type != Datatype::Unknown && expect_type != Datatype::int32 {
162 let msg = format!("expected {expect_type}, got {dtype}");
163 warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
164 }
165 } else if let Ok(dtype) = Datatype::from_str(dtype) {
166 if expect_type != Datatype::Unknown && expect_type != dtype {
167 let msg = format!("expected {expect_type}, got {dtype}");
168 warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
169 }
170 } else {
171 let msg = format!("unrecognized datatype {dtype}");
172 warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
173 }
174 } else if expect_type != Datatype::Unknown && expect_type != Datatype::CString {
175 let msg = format!("expected {expect_type}, got CString");
176 warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
177 }
178 }
179 }
180 }
181 Arg::IType(itype) => match arg {
182 CodeArg::Chain(chain) => {
183 validate_datatypes(chain, data, sc, dc, Datatype::CString, lang, format, false);
184 }
185 CodeArg::Literal(token) => {
186 data.verify_exists(itype, token);
187 }
188 },
189 Arg::Choice(choices) => match arg {
190 CodeArg::Chain(chain) => {
191 validate_datatypes(chain, data, sc, dc, Datatype::CString, lang, format, false);
192 }
193 CodeArg::Literal(token) => {
194 if !choices.contains(&token.as_str()) {
195 let msg = format!("expected one of {}", choices.join(", "));
196 err(ErrorKey::Choice).weak().msg(msg).loc(token).push();
197 }
198 }
199 },
200 }
201}
202
203#[allow(unused_variables)] #[allow(clippy::too_many_arguments)] pub fn validate_datatypes(
218 chain: &CodeChain,
219 data: &Everything,
220 sc: &mut ScopeContext,
221 dc: &DataContext,
222 expect_type: Datatype,
223 lang: Option<Language>,
224 format: Option<&Token>,
225 expect_promote: bool,
226) -> Datatype {
227 let mut curtype = Datatype::Unknown;
228 #[allow(unused_mut)] let mut codes = Cow::from(&chain.codes[..]);
230 #[cfg(any(feature = "ck3", feature = "vic3"))]
231 let mut macro_count = 0;
232 let mut i = 0;
234 let mut in_variable = false;
235 while i < codes.len() {
236 #[cfg(any(feature = "ck3", feature = "vic3"))]
237 if Game::is_ck3() || Game::is_vic3() {
238 while let Some(binding) = data.data_bindings.get(codes[i].name.as_str()) {
239 if let Some(replacement) = binding.replace(&codes[i]) {
240 macro_count += 1;
241 if macro_count > 255 {
242 let msg =
243 format!("substituted data bindings {macro_count} times, giving up");
244 err(ErrorKey::Macro).msg(msg).loc(&codes[i].name).push();
245 return Datatype::Unknown;
246 }
247 codes.to_mut().splice(i..=i, replacement.codes);
248 } else {
249 return Datatype::Unknown;
250 }
251 }
252 }
253
254 let code = &codes[i];
255 let is_first = i == 0;
256 let is_last = i == codes.len() - 1;
257 let mut args = Args::Args(&[]);
258 let mut rtype = Datatype::Unknown;
259
260 if code.name.is("") {
261 warn(ErrorKey::Datafunctions).msg("empty fragment").loc(&code.name).push();
263 return Datatype::Unknown;
264 }
265
266 let lookup_gf = lookup_global_function(code.name.as_str());
267 let lookup_gp = lookup_global_promote(code.name.as_str());
268 let lookup_f = lookup_function(code.name.as_str(), curtype);
269 let lookup_p = lookup_promote(code.name.as_str(), curtype);
270
271 let gf_found = lookup_gf.is_some();
272 let gp_found = lookup_gp.is_some();
273 let f_found = !matches!(lookup_f, LookupResult::NotFound);
274 let p_found = !matches!(lookup_p, LookupResult::NotFound);
275
276 let mut found = false;
277
278 if is_first && is_last && !expect_promote {
279 if let Some((xargs, xrtype)) = lookup_gf {
280 found = true;
281 args = xargs;
282 rtype = xrtype;
283 }
284 } else if is_first && (!is_last || expect_promote) {
285 if let Some((xargs, xrtype)) = lookup_gp {
286 found = true;
287 args = xargs;
288 rtype = xrtype;
289 }
290 } else if !is_first && (!is_last || expect_promote) {
291 match lookup_p {
292 LookupResult::Found(xargs, xrtype) => {
293 found = true;
294 args = xargs;
295 rtype = xrtype;
296 }
297 LookupResult::WrongType => {
298 let msg = format!("{} cannot follow a {curtype} promote", code.name);
299 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
300 return Datatype::Unknown;
301 }
302 LookupResult::NotFound => (),
303 }
304 } else if !is_first && is_last && !expect_promote {
305 match lookup_f {
306 LookupResult::Found(xargs, xrtype) => {
307 found = true;
308 args = xargs;
309 rtype = xrtype;
310 }
311 LookupResult::WrongType => {
312 let msg = format!("{} cannot follow a {curtype} promote", code.name);
313 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
314 return Datatype::Unknown;
315 }
316 LookupResult::NotFound => (),
317 }
318 }
319
320 if Game::is_hoi4() && !found && !is_first && code.name.is("FROM") {
321 found = true;
323 rtype = Datatype::Unknown;
325 } else if Game::is_hoi4() && !found && !is_first && code.name.is("OWNER") {
326 found = true;
328 rtype = Datatype::Unknown;
330 }
331
332 if !found {
333 if is_first && (p_found || f_found) && !gp_found && !gf_found {
336 let msg = format!("{} cannot be the first in a chain", code.name);
337 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
338 return Datatype::Unknown;
339 }
340 if is_last && (gp_found || p_found) && !gf_found && !f_found && !expect_promote {
341 let msg = format!("{} cannot be last in a chain", code.name);
342 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
343 return Datatype::Unknown;
344 }
345 if expect_promote && (gf_found || f_found) {
346 let msg = format!("{} cannot be used in this field", code.name);
347 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
348 return Datatype::Unknown;
349 }
350 if !is_first && (gp_found || gf_found) && !p_found && !f_found {
351 let msg = format!("{} must be the first in a chain", code.name);
352 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
353 return Datatype::Unknown;
354 }
355 if !is_last && (gf_found || f_found) && !gp_found && !p_found {
356 let msg = format!("{} must be last in the chain", code.name);
357 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
358 return Datatype::Unknown;
359 }
360 if gp_found || gf_found || p_found || f_found {
362 let msg = format!("{} is improperly used here", code.name);
363 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
364 return Datatype::Unknown;
365 }
366 }
367
368 #[cfg(feature = "vic3")]
369 if Game::is_vic3()
371 && !found
372 && is_first
373 && data.item_exists(Item::Country, code.name.as_str())
374 {
375 found = true;
376 args = Args::Args(&[]);
377 rtype = Datatype::Vic3(Vic3Datatype::Country);
378 }
379
380 #[cfg(feature = "imperator")]
381 if Game::is_imperator()
382 && !found
383 && is_first
384 && data.item_exists(Item::Country, code.name.as_str())
385 {
386 found = true;
387 args = Args::Args(&[]);
388 rtype = Datatype::Imperator(ImperatorDatatype::Country);
389 }
390
391 #[cfg(feature = "vic3")]
394 if Game::is_vic3()
395 && !found
396 && is_first
397 && is_last
398 && code.name.as_str().starts_with("concept_")
399 {
400 found = true;
401 if let Some(concept) = code.name.as_str().strip_suffix("_desc") {
402 data.verify_exists_implied(Item::GameConcept, concept, &code.name);
403 } else {
404 data.verify_exists(Item::GameConcept, &code.name);
405 }
406 args = Args::Args(&[]);
407 rtype = Datatype::CString;
408 }
409
410 #[cfg(feature = "eu5")]
413 if Game::is_eu5() && !found && is_first && is_last {
414 found = true;
415 if let Some(concept) = code.name.as_str().strip_suffix("_with_icon") {
416 data.verify_exists_implied(Item::GameConcept, concept, &code.name);
417 } else if let Some(concept) = code.name.as_str().strip_suffix("_icon") {
418 data.verify_exists_implied(Item::GameConcept, concept, &code.name);
419 } else {
420 data.verify_exists(Item::GameConcept, &code.name);
421 }
422 args = Args::Args(&[]);
423 rtype = Datatype::CString;
424 }
425
426 #[cfg(feature = "ck3")]
427 if Game::is_ck3()
428 && !found
429 && is_first
430 && is_last
431 && data.item_exists(Item::GameConcept, code.name.as_str())
432 {
433 let game_concept_formatting =
434 format.is_some_and(|fmt| fmt.as_str().contains('E') || fmt.as_str().contains('e'));
435 if sc.is_name_defined(code.name.as_str(), data).is_some() && !game_concept_formatting {
448 let msg = format!("`{}` is both a named scope and a game concept here", &code.name);
449 let info = format!(
450 "The game concept will take precedence. Do `{}.Self` if you want the named scope.",
451 &code.name
452 );
453 warn(ErrorKey::Datafunctions).msg(msg).info(info).loc(&code.name).push();
454 }
455
456 found = true;
457 args = Args::Args(&[]);
458 rtype = Datatype::CString;
459 }
460
461 if Game::is_hoi4() && !found && in_variable {
462 in_variable = false;
464 found = true;
465 rtype = Datatype::Unknown;
467 }
468
469 if !found
475 && is_first
476 && let Some(scopes) = sc.is_name_defined(code.name.as_str(), data)
477 {
478 found = true;
479 args = Args::Args(&[]);
480 rtype = datatype_from_scopes(scopes);
481 }
482
483 let first_char = code.name.as_str().chars().next().unwrap();
488 if !found
489 && is_first
490 && !sc.is_strict()
491 && (first_char.is_lowercase() || first_char.is_ascii_digit())
492 {
493 found = true;
494 args = Args::Args(&[]);
495 rtype = Datatype::Unknown;
499 }
500
501 #[cfg(feature = "hoi4")]
502 if Game::is_hoi4() && !found && is_country_tag(code.name.as_str()) {
503 found = true;
504 data.verify_exists_max_sev(Item::CountryTag, &code.name, Severity::Warning);
505 rtype = Datatype::Hoi4(Hoi4Datatype::Country);
506 }
507
508 #[cfg(feature = "hoi4")]
509 if Game::is_hoi4()
510 && !found
511 && data.item_exists(Item::ScriptedLocalisation, code.name.as_str())
512 {
513 found = true;
514 rtype = Datatype::CString;
515 if let Some((_, block)) =
516 data.get_key_block(Item::ScriptedLocalisation, code.name.as_str())
517 {
518 ScriptedLocalisation::validate_loca_call(block, data, lang);
519 }
520 }
521
522 #[cfg(feature = "hoi4")]
523 if Game::is_hoi4() && !found && code.name.starts_with("?") {
524 found = true;
527 rtype = Datatype::Unknown;
529 let reference = code.name.strip_prefix("?").unwrap();
530 if reference.lowercase_is("global") || reference.is("FROM") || reference.is("PREV") {
532 in_variable = true;
533 } else if is_country_tag(reference.as_str()) {
534 in_variable = true;
535 data.verify_exists_max_sev(Item::CountryTag, &reference, Severity::Warning);
536 } else if reference.is_integer() {
537 in_variable = true;
540 }
541 }
542
543 if !found {
545 let msg = format!("unknown datafunction {}", &code.name);
547 if let Some(alternative) = lookup_alternative(code.name.as_str()) {
548 let info = format!("did you mean {alternative}?");
549 warn(ErrorKey::Datafunctions).msg(msg).info(info).loc(&code.name).push();
550 } else {
551 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
552 }
553 return Datatype::Unknown;
554 }
555
556 if let Args::Args(a) = args
558 && a.len() != code.arguments.len()
559 {
560 let msg = format!(
561 "{} takes {} arguments but was given {} here",
562 code.name,
563 a.len(),
564 code.arguments.len()
565 );
566 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
567 return Datatype::Unknown;
568 }
569
570 #[cfg(feature = "jomini")]
571 if Game::is_jomini() && is_first {
573 let name = if code.name.is("GetScriptedGui") {
574 if let Some(CodeArg::Literal(name)) = code.arguments.first() {
576 Some(name)
577 } else {
578 None
579 }
580 } else if code.name.is("ScriptedGui") {
581 dc.sgui_name()
583 } else {
584 None
585 };
586 if let Some(name) = name {
587 if let Some(code) = codes.get(1)
589 && let Some((key, block, kind)) =
590 data.get_item::<ScriptedGui>(Item::ScriptedGui, name.as_str())
591 {
592 kind.validate_guicall(key, block, data, sc, dc, code);
593 }
594 }
595 }
596
597 #[cfg(feature = "ck3")]
598 if Game::is_ck3()
599 && curtype != Datatype::Ck3(Ck3Datatype::Faith)
600 && (code.name.is("Custom") && code.arguments.len() == 1)
601 || (code.name.is("Custom2") && code.arguments.len() == 2)
602 {
603 if let CodeArg::Literal(ref token) = code.arguments[0] {
605 if let Some(scopes) = scope_from_datatype(curtype) {
606 validate_custom(token, data, scopes, lang);
607 } else if (curtype == Datatype::Unknown
608 || curtype == Datatype::AnyScope
609 || curtype == Datatype::TopScope)
610 && !CUSTOM_RELIGION_LOCAS.contains(&token.as_str())
611 {
612 validate_custom(token, data, Scopes::all(), lang);
614 }
615 }
616 }
617
618 #[cfg(feature = "vic3")]
619 if Game::is_vic3()
620 && code.name.is("GetCustom")
621 && code.arguments.len() == 1
622 && let CodeArg::Literal(ref token) = code.arguments[0]
623 {
624 if let Some(scopes) = scope_from_datatype(curtype) {
625 validate_custom(token, data, scopes, lang);
626 } else if curtype == Datatype::Unknown
627 || curtype == Datatype::AnyScope
628 || curtype == Datatype::TopScope
629 {
630 validate_custom(token, data, Scopes::all(), lang);
632 }
633 }
634
635 #[cfg(feature = "imperator")]
636 if Game::is_imperator()
637 && code.name.is("Custom")
638 && code.arguments.len() == 1
639 && let CodeArg::Literal(ref token) = code.arguments[0]
640 {
641 if let Some(scopes) = scope_from_datatype(curtype) {
642 validate_custom(token, data, scopes, lang);
643 } else if curtype == Datatype::Unknown
644 || curtype == Datatype::AnyScope
645 || curtype == Datatype::TopScope
646 {
647 validate_custom(token, data, Scopes::all(), lang);
649 }
650 }
651
652 #[cfg(feature = "jomini")]
654 if code.name.is("GetDefine")
655 && code.arguments.len() == 2
656 && let CodeArg::Literal(ref token1) = code.arguments[0]
657 && let CodeArg::Literal(ref token2) = code.arguments[1]
658 {
659 let key = format!("{token1}|{token2}");
660 if data.defines.get_bv(&key).is_none() {
661 let msg = format!("{key} not defined in common/defines/");
662 err(ErrorKey::MissingItem).msg(msg).loc(token2).push();
663 }
664 }
665
666 if code.name.is("Localize")
668 && code.arguments.len() == 1
669 && let CodeArg::Literal(ref token) = code.arguments[0]
670 {
671 if token.as_str().is_ascii() {
675 data.localization.verify_exists_lang(token, lang);
676 }
677 }
678
679 #[cfg(feature = "jomini")]
680 if let Args::Args(a) = args {
681 for (i, arg) in a.iter().enumerate() {
682 if Game::is_jomini()
684 && code.name.is("SelectLocalization")
685 && i > 0
686 && let CodeArg::Chain(chain) = &code.arguments[i]
687 && chain.codes.len() == 1
688 && chain.codes[0].arguments.is_empty()
689 && data.item_exists(Item::GameConcept, chain.codes[0].name.as_str())
690 {
691 continue;
692 }
693 validate_argument(&code.arguments[i], data, sc, dc, *arg, lang, format);
694 }
695 }
696
697 curtype = rtype;
698
699 if is_last
700 && curtype != Datatype::Unknown
701 && expect_type != Datatype::Unknown
702 && curtype != expect_type
703 {
704 if expect_type == Datatype::AnyScope {
705 if scope_from_datatype(curtype).is_none() {
706 let msg =
707 format!("{} returns {curtype} but a scope type is needed here", code.name);
708 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
709 return Datatype::Unknown;
710 }
711 } else {
712 let msg =
713 format!("{} returns {curtype} but a {expect_type} is needed here", code.name);
714 warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
715 return Datatype::Unknown;
716 }
717 }
718
719 i += 1;
720 }
721 curtype
722}
723
724fn lookup_global_promote(lookup_name: &str) -> Option<(Args, Datatype)> {
725 let global_promotes_map = match Game::game() {
726 #[cfg(feature = "ck3")]
727 Game::Ck3 => &crate::ck3::tables::datafunctions::GLOBAL_PROMOTES_MAP,
728 #[cfg(feature = "vic3")]
729 Game::Vic3 => &crate::vic3::tables::datafunctions::GLOBAL_PROMOTES_MAP,
730 #[cfg(feature = "imperator")]
731 Game::Imperator => &crate::imperator::tables::datafunctions::GLOBAL_PROMOTES_MAP,
732 #[cfg(feature = "eu5")]
733 Game::Eu5 => &crate::eu5::tables::datafunctions::GLOBAL_PROMOTES_MAP,
734 #[cfg(feature = "hoi4")]
735 Game::Hoi4 => &crate::hoi4::tables::datafunctions::GLOBAL_PROMOTES_MAP,
736 };
737
738 if let result @ Some(_) = global_promotes_map.get(lookup_name).copied() {
739 return result;
740 }
741
742 if let Ok(dtype) = Datatype::from_str(lookup_name) {
744 return Some((Args::Args(&[]), dtype));
745 }
746
747 None
748}
749
750fn lookup_global_function(lookup_name: &str) -> Option<(Args, Datatype)> {
751 let global_functions_map = match Game::game() {
752 #[cfg(feature = "ck3")]
753 Game::Ck3 => &crate::ck3::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
754 #[cfg(feature = "vic3")]
755 Game::Vic3 => &crate::vic3::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
756 #[cfg(feature = "imperator")]
757 Game::Imperator => &crate::imperator::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
758 #[cfg(feature = "eu5")]
759 Game::Eu5 => &crate::eu5::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
760 #[cfg(feature = "hoi4")]
761 Game::Hoi4 => &crate::hoi4::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
762 };
763 global_functions_map.get(lookup_name).copied()
764}
765
766fn lookup_promote_or_function(ltype: Datatype, vec: &[(Datatype, Args, Datatype)]) -> LookupResult {
767 let mut possible_args = None;
768 let mut possible_rtype = None;
769
770 for (intype, args, rtype) in vec.iter().copied() {
771 if ltype == Datatype::Unknown {
772 if possible_rtype.is_none() {
773 possible_args = Some(args);
774 possible_rtype = Some(rtype);
775 } else {
776 if possible_rtype != Some(rtype) {
777 possible_rtype = Some(Datatype::Unknown);
778 }
779 if possible_args != Some(args) {
780 possible_args = Some(Args::Unknown);
781 }
782 }
783 } else if ltype == intype {
784 return LookupResult::Found(args, rtype);
785 }
786 }
787
788 if ltype == Datatype::Unknown {
789 LookupResult::Found(possible_args.unwrap(), possible_rtype.unwrap())
790 } else {
791 LookupResult::WrongType
793 }
794}
795
796fn lookup_promote(lookup_name: &str, ltype: Datatype) -> LookupResult {
797 let promotes_map = match Game::game() {
798 #[cfg(feature = "ck3")]
799 Game::Ck3 => &crate::ck3::tables::datafunctions::PROMOTES_MAP,
800 #[cfg(feature = "vic3")]
801 Game::Vic3 => &crate::vic3::tables::datafunctions::PROMOTES_MAP,
802 #[cfg(feature = "imperator")]
803 Game::Imperator => &crate::imperator::tables::datafunctions::PROMOTES_MAP,
804 #[cfg(feature = "eu5")]
805 Game::Eu5 => &crate::eu5::tables::datafunctions::PROMOTES_MAP,
806 #[cfg(feature = "hoi4")]
807 Game::Hoi4 => &crate::hoi4::tables::datafunctions::PROMOTES_MAP,
808 };
809
810 promotes_map
811 .get(lookup_name)
812 .map_or(LookupResult::NotFound, |x| lookup_promote_or_function(ltype, x))
813}
814
815fn lookup_function(lookup_name: &str, ltype: Datatype) -> LookupResult {
816 let functions_map = match Game::game() {
817 #[cfg(feature = "ck3")]
818 Game::Ck3 => &crate::ck3::tables::datafunctions::FUNCTIONS_MAP,
819 #[cfg(feature = "vic3")]
820 Game::Vic3 => &crate::vic3::tables::datafunctions::FUNCTIONS_MAP,
821 #[cfg(feature = "imperator")]
822 Game::Imperator => &crate::imperator::tables::datafunctions::FUNCTIONS_MAP,
823 #[cfg(feature = "eu5")]
824 Game::Eu5 => &crate::eu5::tables::datafunctions::FUNCTIONS_MAP,
825 #[cfg(feature = "hoi4")]
826 Game::Hoi4 => &crate::hoi4::tables::datafunctions::FUNCTIONS_MAP,
827 };
828
829 functions_map
830 .get(lookup_name)
831 .map_or(LookupResult::NotFound, |x| lookup_promote_or_function(ltype, x))
832}
833
834pub struct CaseInsensitiveStr(pub(crate) &'static str);
835
836impl PartialEq for CaseInsensitiveStr {
837 fn eq(&self, other: &Self) -> bool {
838 self.0.eq_ignore_ascii_case(other.0)
839 }
840}
841
842impl Eq for CaseInsensitiveStr {}
843
844impl std::hash::Hash for CaseInsensitiveStr {
845 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
846 self.0.to_ascii_lowercase().hash(state);
847 }
848}
849
850fn lookup_alternative(lookup_name: &'static str) -> Option<&'static str> {
855 let lowercase_datatype_set = match Game::game() {
856 #[cfg(feature = "ck3")]
857 Game::Ck3 => &crate::ck3::tables::datafunctions::LOWERCASE_DATATYPE_SET,
858 #[cfg(feature = "vic3")]
859 Game::Vic3 => &crate::vic3::tables::datafunctions::LOWERCASE_DATATYPE_SET,
860 #[cfg(feature = "imperator")]
861 Game::Imperator => &crate::imperator::tables::datafunctions::LOWERCASE_DATATYPE_SET,
862 #[cfg(feature = "eu5")]
863 Game::Eu5 => &crate::eu5::tables::datafunctions::LOWERCASE_DATATYPE_SET,
864 #[cfg(feature = "hoi4")]
865 Game::Hoi4 => &crate::hoi4::tables::datafunctions::LOWERCASE_DATATYPE_SET,
866 };
867
868 lowercase_datatype_set.get(&CaseInsensitiveStr(lookup_name)).map(|x| x.0)
869}
870
871fn datatype_and_scope_map() -> &'static LazyLock<BiTigerHashMap<Datatype, Scopes>> {
872 match Game::game() {
873 #[cfg(feature = "ck3")]
874 Game::Ck3 => &crate::ck3::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
875 #[cfg(feature = "vic3")]
876 Game::Vic3 => &crate::vic3::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
877 #[cfg(feature = "imperator")]
878 Game::Imperator => &crate::imperator::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
879 #[cfg(feature = "eu5")]
880 Game::Eu5 => &crate::eu5::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
881 #[cfg(feature = "hoi4")]
882 Game::Hoi4 => &crate::hoi4::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
883 }
884}
885
886pub fn scope_from_datatype(dtype: Datatype) -> Option<Scopes> {
889 datatype_and_scope_map().get_by_left(&dtype).copied()
890}
891
892fn datatype_from_scopes(scopes: Scopes) -> Datatype {
896 datatype_and_scope_map().get_by_right(&scopes).copied().unwrap_or(Datatype::Unknown)
897}