use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use std::sync::LazyLock;
use phf::phf_map;
use strum_macros::{Display, EnumString};
#[cfg(feature = "ck3")]
use crate::ck3::data::religions::CUSTOM_RELIGION_LOCAS;
use crate::context::ScopeContext;
#[cfg(feature = "jomini")]
use crate::data::customloca::CustomLocalization;
use crate::data::localization::Language;
use crate::everything::Everything;
use crate::game::Game;
#[cfg(feature = "hoi4")]
use crate::helpers::is_country_tag;
use crate::helpers::BiTigerHashMap;
#[cfg(feature = "hoi4")]
use crate::hoi4::data::scripted_localisation::ScriptedLocalisation;
use crate::item::Item;
#[cfg(feature = "jomini")]
use crate::report::err;
#[cfg(feature = "hoi4")]
use crate::report::Severity;
use crate::report::{warn, ErrorKey};
use crate::scopes::Scopes;
use crate::token::Token;
#[cfg(feature = "ck3")]
include!("ck3/tables/include/datatypes.rs");
#[cfg(feature = "vic3")]
include!("vic3/tables/include/datatypes.rs");
#[cfg(feature = "imperator")]
include!("imperator/tables/include/datatypes.rs");
#[cfg(feature = "hoi4")]
include!("hoi4/tables/include/datatypes.rs");
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[allow(non_camel_case_types)]
pub enum Datatype {
Unknown,
AnyScope,
CFixedPoint,
CString,
CUTF8String,
CVector2f,
CVector2i,
CVector3f,
CVector3i,
CVector4f,
CVector4i,
Date,
Scope,
TopScope,
bool,
double,
float,
int16,
int32,
int64,
int8,
uint16,
uint32,
uint64,
uint8,
void,
#[cfg(feature = "ck3")]
Ck3(Ck3Datatype),
#[cfg(feature = "vic3")]
Vic3(Vic3Datatype),
#[cfg(feature = "imperator")]
Imperator(ImperatorDatatype),
#[cfg(feature = "hoi4")]
Hoi4(Hoi4Datatype),
}
static STR_DATATYPE_MAP: phf::Map<&'static str, Datatype> = phf_map! {
"Unknown" => Datatype::Unknown,
"AnyScope" => Datatype::AnyScope,
"CFixedPoint" => Datatype::CFixedPoint,
"CString" => Datatype::CString,
"CUTF8String" => Datatype::CUTF8String,
"CVector2f" => Datatype::CVector2f,
"CVector2i" => Datatype::CVector2i,
"CVector3f" => Datatype::CVector3f,
"CVector3i" => Datatype::CVector3i,
"CVector4f" => Datatype::CVector4f,
"CVector4i" => Datatype::CVector4i,
"Date" => Datatype::Date,
"Scope" => Datatype::Scope,
"TopScope" => Datatype::TopScope,
"bool" => Datatype::bool,
"double" => Datatype::double,
"float" => Datatype::float,
"int16" => Datatype::int16,
"int32" => Datatype::int32,
"int64" => Datatype::int64,
"int8" => Datatype::int8,
"uint16" => Datatype::uint16,
"uint32" => Datatype::uint32,
"uint64" => Datatype::uint64,
"uint8" => Datatype::uint8,
"void" => Datatype::void,
};
impl FromStr for Datatype {
type Err = strum::ParseError;
fn from_str(s: &str) -> Result<Self, strum::ParseError> {
STR_DATATYPE_MAP.get(s).copied().ok_or(strum::ParseError::VariantNotFound).or_else(|_| {
match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => Ck3Datatype::from_str(s).map(Datatype::Ck3),
#[cfg(feature = "vic3")]
Game::Vic3 => Vic3Datatype::from_str(s).map(Datatype::Vic3),
#[cfg(feature = "imperator")]
Game::Imperator => ImperatorDatatype::from_str(s).map(Datatype::Imperator),
#[cfg(feature = "hoi4")]
Game::Hoi4 => Hoi4Datatype::from_str(s).map(Datatype::Hoi4),
}
})
}
}
impl Display for Datatype {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
match *self {
Datatype::Unknown => write!(f, "Unknown"),
Datatype::AnyScope => write!(f, "AnyScope"),
Datatype::CFixedPoint => write!(f, "CFixedPoint"),
Datatype::CString => write!(f, "CString"),
Datatype::CUTF8String => write!(f, "CUTF8String"),
Datatype::CVector2f => write!(f, "CVector2f"),
Datatype::CVector2i => write!(f, "CVector2i"),
Datatype::CVector3f => write!(f, "CVector3f"),
Datatype::CVector3i => write!(f, "CVector3i"),
Datatype::CVector4f => write!(f, "CVector4f"),
Datatype::CVector4i => write!(f, "CVector4i"),
Datatype::Date => write!(f, "Date"),
Datatype::Scope => write!(f, "Scope"),
Datatype::TopScope => write!(f, "TopScope"),
Datatype::bool => write!(f, "bool"),
Datatype::double => write!(f, "double"),
Datatype::float => write!(f, "float"),
Datatype::int16 => write!(f, "int16"),
Datatype::int32 => write!(f, "int32"),
Datatype::int64 => write!(f, "int64"),
Datatype::int8 => write!(f, "int8"),
Datatype::uint16 => write!(f, "uint16"),
Datatype::uint32 => write!(f, "uint32"),
Datatype::uint64 => write!(f, "uint64"),
Datatype::uint8 => write!(f, "uint8"),
Datatype::void => write!(f, "void"),
#[cfg(feature = "ck3")]
Datatype::Ck3(dt) => dt.fmt(f),
#[cfg(feature = "vic3")]
Datatype::Vic3(dt) => dt.fmt(f),
#[cfg(feature = "imperator")]
Datatype::Imperator(dt) => dt.fmt(f),
#[cfg(feature = "hoi4")]
Datatype::Hoi4(dt) => dt.fmt(f),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct CodeChain {
pub codes: Box<[Code]>,
}
#[derive(Clone, Debug)]
pub struct Code {
pub name: Token,
pub arguments: Vec<CodeArg>,
}
#[derive(Clone, Debug)]
#[allow(dead_code)] pub enum CodeArg {
Chain(CodeChain),
Literal(Token),
}
impl CodeChain {
#[cfg(feature = "ck3")]
pub fn as_gameconcept(&self) -> Option<&Token> {
if self.codes.len() == 1 && self.codes[0].arguments.is_empty() {
Some(&self.codes[0].name)
} else if self.codes.len() == 1
&& self.codes[0].name.is("Concept")
&& self.codes[0].arguments.len() == 2
{
if let CodeArg::Literal(token) = &self.codes[0].arguments[0] {
Some(token)
} else {
None
}
} else {
None
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Arg {
#[cfg(feature = "jomini")]
DType(Datatype),
#[cfg(feature = "jomini")]
IType(Item),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Args {
Unknown,
Args(&'static [Arg]),
}
#[derive(Copy, Clone, Debug)]
enum LookupResult {
NotFound,
WrongType,
Found(Args, Datatype),
}
#[cfg(feature = "jomini")]
fn validate_custom(token: &Token, data: &Everything, scopes: Scopes, lang: Option<Language>) {
data.verify_exists(Item::CustomLocalization, token);
if let Some((key, block)) = data.get_key_block(Item::CustomLocalization, token.as_str()) {
CustomLocalization::validate_custom_call(key, block, data, token, scopes, lang, "", None);
}
}
#[cfg(feature = "jomini")]
fn validate_argument(
arg: &CodeArg,
data: &Everything,
sc: &mut ScopeContext,
expect_arg: Arg,
lang: Option<Language>,
format: Option<&Token>,
) {
match expect_arg {
Arg::DType(expect_type) => {
match arg {
CodeArg::Chain(chain) => {
validate_datatypes(chain, data, sc, expect_type, lang, format, false);
}
CodeArg::Literal(token) => {
if token.as_str().starts_with('(') && token.as_str().contains(')') {
let dtype =
token.as_str().split(')').next().unwrap().strip_prefix('(').unwrap();
if dtype == "hex" {
if expect_type != Datatype::Unknown && expect_type != Datatype::int32 {
let msg = format!("expected {expect_type}, got {dtype}");
warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
}
} else if let Ok(dtype) = Datatype::from_str(dtype) {
if expect_type != Datatype::Unknown && expect_type != dtype {
let msg = format!("expected {expect_type}, got {dtype}");
warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
}
} else {
let msg = format!("unrecognized datatype {dtype}");
warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
}
} else if expect_type != Datatype::Unknown && expect_type != Datatype::CString {
let msg = format!("expected {expect_type}, got CString");
warn(ErrorKey::Datafunctions).msg(msg).loc(token).push();
}
}
}
}
Arg::IType(itype) => match arg {
CodeArg::Chain(chain) => {
validate_datatypes(chain, data, sc, Datatype::CString, lang, format, false);
}
CodeArg::Literal(token) => {
data.verify_exists(itype, token);
}
},
}
}
#[allow(unused_variables)] pub fn validate_datatypes(
chain: &CodeChain,
data: &Everything,
sc: &mut ScopeContext,
expect_type: Datatype,
lang: Option<Language>,
format: Option<&Token>,
expect_promote: bool,
) {
let mut curtype = Datatype::Unknown;
#[allow(unused_mut)] let mut codes = Cow::from(&chain.codes[..]);
#[cfg(any(feature = "ck3", feature = "vic3"))]
let mut macro_count = 0;
let mut i = 0;
let mut in_variable = false;
while i < codes.len() {
#[cfg(any(feature = "ck3", feature = "vic3"))]
if Game::is_ck3() || Game::is_vic3() {
while let Some(binding) = data.data_bindings.get(codes[i].name.as_str()) {
if let Some(replacement) = binding.replace(&codes[i]) {
macro_count += 1;
if macro_count > 255 {
let msg =
format!("substituted data bindings {macro_count} times, giving up");
err(ErrorKey::Macro).msg(msg).loc(&codes[i].name).push();
return;
}
codes.to_mut().splice(i..=i, replacement.codes);
} else {
return;
}
}
}
let code = &codes[i];
let is_first = i == 0;
let is_last = i == codes.len() - 1;
let mut args = Args::Args(&[]);
let mut rtype = Datatype::Unknown;
if code.name.is("") {
warn(ErrorKey::Datafunctions).msg("empty fragment").loc(&code.name).push();
return;
}
let lookup_gf = lookup_global_function(code.name.as_str());
let lookup_gp = lookup_global_promote(code.name.as_str());
let lookup_f = lookup_function(code.name.as_str(), curtype);
let lookup_p = lookup_promote(code.name.as_str(), curtype);
let gf_found = lookup_gf.is_some();
let gp_found = lookup_gp.is_some();
let f_found = !matches!(lookup_f, LookupResult::NotFound);
let p_found = !matches!(lookup_p, LookupResult::NotFound);
let mut found = false;
if is_first && is_last && !expect_promote {
if let Some((xargs, xrtype)) = lookup_gf {
found = true;
args = xargs;
rtype = xrtype;
}
} else if is_first && (!is_last || expect_promote) {
if let Some((xargs, xrtype)) = lookup_gp {
found = true;
args = xargs;
rtype = xrtype;
}
} else if !is_first && (!is_last || expect_promote) {
match lookup_p {
LookupResult::Found(xargs, xrtype) => {
found = true;
args = xargs;
rtype = xrtype;
}
LookupResult::WrongType => {
let msg = format!("{} cannot follow a {curtype} promote", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
LookupResult::NotFound => (),
}
} else if !is_first && is_last && !expect_promote {
match lookup_f {
LookupResult::Found(xargs, xrtype) => {
found = true;
args = xargs;
rtype = xrtype;
}
LookupResult::WrongType => {
let msg = format!("{} cannot follow a {curtype} promote", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
LookupResult::NotFound => (),
}
}
if Game::is_hoi4() && !found && !is_first && code.name.is("FROM") {
found = true;
rtype = Datatype::Unknown;
} else if Game::is_hoi4() && !found && !is_first && code.name.is("OWNER") {
found = true;
rtype = Datatype::Unknown;
}
if !found {
if is_first && (p_found || f_found) && !gp_found && !gf_found {
let msg = format!("{} cannot be the first in a chain", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
if is_last && (gp_found || p_found) && !gf_found && !f_found && !expect_promote {
let msg = format!("{} cannot be last in a chain", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
if expect_promote && (gf_found || f_found) {
let msg = format!("{} cannot be used in this field", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
if !is_first && (gp_found || gf_found) && !p_found && !f_found {
let msg = format!("{} must be the first in a chain", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
if !is_last && (gf_found || f_found) && !gp_found && !p_found {
let msg = format!("{} must be last in the chain", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
if gp_found || gf_found || p_found || f_found {
let msg = format!("{} is improperly used here", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
}
#[cfg(feature = "vic3")]
if Game::is_vic3()
&& !found
&& is_first
&& data.item_exists(Item::Country, code.name.as_str())
{
found = true;
args = Args::Args(&[]);
rtype = Datatype::Vic3(Vic3Datatype::Country);
}
#[cfg(feature = "imperator")]
if Game::is_imperator()
&& !found
&& is_first
&& data.item_exists(Item::Country, code.name.as_str())
{
found = true;
args = Args::Args(&[]);
rtype = Datatype::Imperator(ImperatorDatatype::Country);
}
#[cfg(feature = "vic3")]
if Game::is_vic3()
&& !found
&& is_first
&& is_last
&& code.name.as_str().starts_with("concept_")
{
found = true;
if let Some(concept) = code.name.as_str().strip_suffix("_desc") {
data.verify_exists_implied(Item::GameConcept, concept, &code.name);
} else {
data.verify_exists(Item::GameConcept, &code.name);
}
args = Args::Args(&[]);
rtype = Datatype::CString;
}
#[cfg(feature = "ck3")]
if Game::is_ck3()
&& !found
&& is_first
&& is_last
&& data.item_exists(Item::GameConcept, code.name.as_str())
{
let game_concept_formatting =
format.is_some_and(|fmt| fmt.as_str().contains('E') || fmt.as_str().contains('e'));
if sc.is_name_defined(code.name.as_str()).is_some() && !game_concept_formatting {
let msg = format!("`{}` is both a named scope and a game concept here", &code.name);
let info = format!("The game concept will take precedence. Do `{}.Self` if you want the named scope.", &code.name);
warn(ErrorKey::Datafunctions).msg(msg).info(info).loc(&code.name).push();
}
found = true;
args = Args::Args(&[]);
rtype = Datatype::CString;
}
if Game::is_hoi4() && !found && in_variable {
in_variable = false;
found = true;
rtype = Datatype::Unknown;
}
if !found && is_first {
if let Some(scopes) = sc.is_name_defined(code.name.as_str()) {
found = true;
args = Args::Args(&[]);
rtype = datatype_from_scopes(scopes);
}
}
let first_char = code.name.as_str().chars().next().unwrap();
if !found
&& is_first
&& !sc.is_strict()
&& (first_char.is_lowercase() || first_char.is_ascii_digit())
{
found = true;
args = Args::Args(&[]);
rtype = Datatype::Unknown;
}
#[cfg(feature = "hoi4")]
if Game::is_hoi4() && !found && is_country_tag(code.name.as_str()) {
found = true;
data.verify_exists_max_sev(Item::CountryTag, &code.name, Severity::Warning);
rtype = Datatype::Hoi4(Hoi4Datatype::Country);
}
#[cfg(feature = "hoi4")]
if Game::is_hoi4()
&& !found
&& data.item_exists(Item::ScriptedLocalisation, code.name.as_str())
{
found = true;
rtype = Datatype::CString;
if let Some((_, block)) =
data.get_key_block(Item::ScriptedLocalisation, code.name.as_str())
{
ScriptedLocalisation::validate_loca_call(block, data, lang);
}
}
#[cfg(feature = "hoi4")]
if Game::is_hoi4() && !found && code.name.starts_with("?") {
found = true;
rtype = Datatype::Unknown;
let reference = code.name.strip_prefix("?").unwrap();
if reference.lowercase_is("global") || reference.is("FROM") || reference.is("PREV") {
in_variable = true;
} else if is_country_tag(reference.as_str()) {
in_variable = true;
data.verify_exists_max_sev(Item::CountryTag, &reference, Severity::Warning);
} else if reference.is_integer() {
in_variable = true;
}
}
if !found {
let msg = format!("unknown datafunction {}", &code.name);
if let Some(alternative) = lookup_alternative(code.name.as_str()) {
let info = format!("did you mean {alternative}?");
warn(ErrorKey::Datafunctions).msg(msg).info(info).loc(&code.name).push();
} else {
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
}
return;
}
if let Args::Args(a) = args {
if a.len() != code.arguments.len() {
let msg = format!(
"{} takes {} arguments but was given {} here",
code.name,
a.len(),
code.arguments.len()
);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
}
#[cfg(feature = "ck3")]
if Game::is_ck3()
&& curtype != Datatype::Ck3(Ck3Datatype::Faith)
&& (code.name.is("Custom") && code.arguments.len() == 1)
|| (code.name.is("Custom2") && code.arguments.len() == 2)
{
if let CodeArg::Literal(ref token) = code.arguments[0] {
if let Some(scopes) = scope_from_datatype(curtype) {
validate_custom(token, data, scopes, lang);
} else if (curtype == Datatype::Unknown
|| curtype == Datatype::AnyScope
|| curtype == Datatype::TopScope)
&& !CUSTOM_RELIGION_LOCAS.contains(&token.as_str())
{
validate_custom(token, data, Scopes::all(), lang);
}
}
}
#[cfg(feature = "vic3")]
if Game::is_vic3() && code.name.is("GetCustom") && code.arguments.len() == 1 {
if let CodeArg::Literal(ref token) = code.arguments[0] {
if let Some(scopes) = scope_from_datatype(curtype) {
validate_custom(token, data, scopes, lang);
} else if curtype == Datatype::Unknown
|| curtype == Datatype::AnyScope
|| curtype == Datatype::TopScope
{
validate_custom(token, data, Scopes::all(), lang);
}
}
}
#[cfg(feature = "imperator")]
if Game::is_imperator() && code.name.is("Custom") && code.arguments.len() == 1 {
if let CodeArg::Literal(ref token) = code.arguments[0] {
if let Some(scopes) = scope_from_datatype(curtype) {
validate_custom(token, data, scopes, lang);
} else if curtype == Datatype::Unknown
|| curtype == Datatype::AnyScope
|| curtype == Datatype::TopScope
{
validate_custom(token, data, Scopes::all(), lang);
}
}
}
#[cfg(feature = "jomini")]
if code.name.is("GetDefine") && code.arguments.len() == 2 {
if let CodeArg::Literal(ref token1) = code.arguments[0] {
if let CodeArg::Literal(ref token2) = code.arguments[1] {
let key = format!("{token1}|{token2}");
if data.defines.get_bv(&key).is_none() {
let msg = format!("{key} not defined in common/defines/");
err(ErrorKey::MissingItem).msg(msg).loc(token2).push();
}
}
}
}
if code.name.is("Localize") && code.arguments.len() == 1 {
if let CodeArg::Literal(ref token) = code.arguments[0] {
if token.as_str().is_ascii() {
data.localization.verify_exists_lang(token, lang);
}
}
}
#[cfg(feature = "jomini")]
if let Args::Args(a) = args {
for (i, arg) in a.iter().enumerate() {
if Game::is_jomini() && code.name.is("SelectLocalization") && i > 0 {
if let CodeArg::Chain(chain) = &code.arguments[i] {
if chain.codes.len() == 1
&& chain.codes[0].arguments.is_empty()
&& data.item_exists(Item::GameConcept, chain.codes[0].name.as_str())
{
continue;
}
}
}
validate_argument(&code.arguments[i], data, sc, *arg, lang, format);
}
}
curtype = rtype;
if is_last
&& curtype != Datatype::Unknown
&& expect_type != Datatype::Unknown
&& curtype != expect_type
{
if expect_type == Datatype::AnyScope {
if scope_from_datatype(curtype).is_none() {
let msg =
format!("{} returns {curtype} but a scope type is needed here", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
} else {
let msg =
format!("{} returns {curtype} but a {expect_type} is needed here", code.name);
warn(ErrorKey::Datafunctions).msg(msg).loc(&code.name).push();
return;
}
}
i += 1;
}
}
fn lookup_global_promote(lookup_name: &str) -> Option<(Args, Datatype)> {
let global_promotes_map = match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => &crate::ck3::tables::datafunctions::GLOBAL_PROMOTES_MAP,
#[cfg(feature = "vic3")]
Game::Vic3 => &crate::vic3::tables::datafunctions::GLOBAL_PROMOTES_MAP,
#[cfg(feature = "imperator")]
Game::Imperator => &crate::imperator::tables::datafunctions::GLOBAL_PROMOTES_MAP,
#[cfg(feature = "hoi4")]
Game::Hoi4 => &crate::hoi4::tables::datafunctions::GLOBAL_PROMOTES_MAP,
};
if let result @ Some(_) = global_promotes_map.get(lookup_name).copied() {
return result;
}
if let Ok(dtype) = Datatype::from_str(lookup_name) {
return Some((Args::Args(&[]), dtype));
}
None
}
fn lookup_global_function(lookup_name: &str) -> Option<(Args, Datatype)> {
let global_functions_map = match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => &crate::ck3::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
#[cfg(feature = "vic3")]
Game::Vic3 => &crate::vic3::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
#[cfg(feature = "imperator")]
Game::Imperator => &crate::imperator::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
#[cfg(feature = "hoi4")]
Game::Hoi4 => &crate::hoi4::tables::datafunctions::GLOBAL_FUNCTIONS_MAP,
};
global_functions_map.get(lookup_name).copied()
}
fn lookup_promote_or_function(ltype: Datatype, vec: &[(Datatype, Args, Datatype)]) -> LookupResult {
let mut possible_args = None;
let mut possible_rtype = None;
for (intype, args, rtype) in vec.iter().copied() {
if ltype == Datatype::Unknown {
if possible_rtype.is_none() {
possible_args = Some(args);
possible_rtype = Some(rtype);
} else {
if possible_rtype != Some(rtype) {
possible_rtype = Some(Datatype::Unknown);
}
if possible_args != Some(args) {
possible_args = Some(Args::Unknown);
}
}
} else if ltype == intype {
return LookupResult::Found(args, rtype);
}
}
if ltype == Datatype::Unknown {
LookupResult::Found(possible_args.unwrap(), possible_rtype.unwrap())
} else {
LookupResult::WrongType
}
}
fn lookup_promote(lookup_name: &str, ltype: Datatype) -> LookupResult {
let promotes_map = match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => &crate::ck3::tables::datafunctions::PROMOTES_MAP,
#[cfg(feature = "vic3")]
Game::Vic3 => &crate::vic3::tables::datafunctions::PROMOTES_MAP,
#[cfg(feature = "imperator")]
Game::Imperator => &crate::imperator::tables::datafunctions::PROMOTES_MAP,
#[cfg(feature = "hoi4")]
Game::Hoi4 => &crate::hoi4::tables::datafunctions::PROMOTES_MAP,
};
promotes_map
.get(lookup_name)
.map_or(LookupResult::NotFound, |x| lookup_promote_or_function(ltype, x))
}
fn lookup_function(lookup_name: &str, ltype: Datatype) -> LookupResult {
let functions_map = match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => &crate::ck3::tables::datafunctions::FUNCTIONS_MAP,
#[cfg(feature = "vic3")]
Game::Vic3 => &crate::vic3::tables::datafunctions::FUNCTIONS_MAP,
#[cfg(feature = "imperator")]
Game::Imperator => &crate::imperator::tables::datafunctions::FUNCTIONS_MAP,
#[cfg(feature = "hoi4")]
Game::Hoi4 => &crate::hoi4::tables::datafunctions::FUNCTIONS_MAP,
};
functions_map
.get(lookup_name)
.map_or(LookupResult::NotFound, |x| lookup_promote_or_function(ltype, x))
}
pub struct CaseInsensitiveStr(pub(crate) &'static str);
impl PartialEq for CaseInsensitiveStr {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(other.0)
}
}
impl Eq for CaseInsensitiveStr {}
impl std::hash::Hash for CaseInsensitiveStr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_ascii_lowercase().hash(state);
}
}
fn lookup_alternative(lookup_name: &'static str) -> Option<&'static str> {
let lowercase_datatype_set = match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => &crate::ck3::tables::datafunctions::LOWERCASE_DATATYPE_SET,
#[cfg(feature = "vic3")]
Game::Vic3 => &crate::vic3::tables::datafunctions::LOWERCASE_DATATYPE_SET,
#[cfg(feature = "imperator")]
Game::Imperator => &crate::imperator::tables::datafunctions::LOWERCASE_DATATYPE_SET,
#[cfg(feature = "hoi4")]
Game::Hoi4 => &crate::hoi4::tables::datafunctions::LOWERCASE_DATATYPE_SET,
};
lowercase_datatype_set.get(&CaseInsensitiveStr(lookup_name)).map(|x| x.0)
}
fn datatype_and_scope_map() -> &'static LazyLock<BiTigerHashMap<Datatype, Scopes>> {
match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => &crate::ck3::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
#[cfg(feature = "vic3")]
Game::Vic3 => &crate::vic3::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
#[cfg(feature = "imperator")]
Game::Imperator => &crate::imperator::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
#[cfg(feature = "hoi4")]
Game::Hoi4 => &crate::hoi4::tables::datafunctions::DATATYPE_AND_SCOPE_MAP,
}
}
fn scope_from_datatype(dtype: Datatype) -> Option<Scopes> {
datatype_and_scope_map().get_by_left(&dtype).copied()
}
fn datatype_from_scopes(scopes: Scopes) -> Datatype {
datatype_and_scope_map().get_by_right(&scopes).copied().unwrap_or(Datatype::Unknown)
}