use ahash::{HashMap, HashSet, RandomState};
use bimap::BiHashMap;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use crate::game::Game;
use crate::item::Item;
#[cfg(any(feature = "vic3", feature = "eu5"))]
use crate::report::err;
use crate::report::{ErrorKey, tips, warn};
#[cfg(feature = "hoi4")]
use crate::scopes::Scopes;
use crate::token::Token;
pub type TigerHashMap<K, V> = HashMap<K, V>;
pub use ahash::HashMapExt as TigerHashMapExt;
pub type TigerHashSet<T> = HashSet<T>;
pub use ahash::HashSetExt as TigerHashSetExt;
#[macro_export]
macro_rules! set {
( $x:expr ) => {
ahash::AHashSet::from($x).into()
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AllowInject {
No,
Yes,
}
pub fn dup_error(key: &Token, other: &Token, id: &str) {
warn(ErrorKey::DuplicateItem)
.msg(format!("{id} is redefined by another {id}"))
.loc(other)
.loc_msg(key, format!("the other {id} is here"))
.push();
}
pub fn exact_dup_error(key: &Token, other: &Token, id: &str) {
warn(ErrorKey::ExactDuplicateItem)
.msg(format!("{id} is redefined by an identical {id}"))
.loc(other)
.loc_msg(key, format!("the other {id} is here"))
.push();
}
pub fn exact_dup_advice(key: &Token, other: &Token, id: &str) {
tips(ErrorKey::ExactDuplicateItem)
.msg(format!("{id} is redefined by an identical {id}, which may cause problems if one of them is later changed"))
.loc(other)
.loc_msg(key, format!("the other {id} is here"))
.push();
}
pub fn dup_assign_error(key: &Token, other: &Token, allow_inject: AllowInject) {
if allow_inject == AllowInject::Yes && key.loc.kind > other.loc.kind {
return;
}
let mut key = key.clone();
key.loc.link_idx = None;
let mut other = other.clone();
other.loc.link_idx = None;
warn(ErrorKey::DuplicateField)
.msg(format!("`{other}` is redefined in a following line").as_str())
.loc(other.loc)
.loc_msg(key.loc, "the other one is here")
.push();
}
pub fn display_choices(f: &mut Formatter, v: &[&str], joiner: &str) -> Result<(), std::fmt::Error> {
for i in 0..v.len() {
write!(f, "{}", v[i])?;
if i + 1 == v.len() {
} else if i + 2 == v.len() {
write!(f, " {joiner} ")?;
} else {
write!(f, ", ")?;
}
}
Ok(())
}
enum Choices<'a> {
OrChoices(&'a [&'a str]),
AndChoices(&'a [&'a str]),
}
impl Display for Choices<'_> {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
match self {
Choices::OrChoices(cs) => display_choices(f, cs, "or"),
Choices::AndChoices(cs) => display_choices(f, cs, "and"),
}
}
}
pub fn stringify_choices(v: &[&str]) -> String {
format!("{}", Choices::OrChoices(v))
}
pub fn stringify_list(v: &[&str]) -> String {
format!("{}", Choices::AndChoices(v))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg(feature = "jomini")]
pub enum TriBool {
True,
False,
Maybe,
}
pub const BANNED_NAMES: &[&str] = &[
"if",
"else",
"else_if",
"trigger_if",
"trigger_else",
"trigger_else_if",
"while",
"limit",
"filter",
"switch",
"take_hostage", ];
pub(crate) type BiTigerHashMap<L, R> = BiHashMap<L, R, RandomState, RandomState>;
#[derive(Debug, Clone)]
pub(crate) enum ActionOrEvent {
Action(Token),
Event(Token, &'static str, usize),
}
impl ActionOrEvent {
pub(crate) fn new_action(key: Token) -> Self {
Self::Action(key)
}
pub(crate) fn new_event(key: Token) -> Self {
if let Some((namespace, nr)) = key.as_str().split_once('.')
&& let Ok(nr) = usize::from_str(nr)
{
return Self::Event(key, namespace, nr);
}
let namespace = key.as_str();
Self::Event(key, namespace, 0)
}
pub(crate) fn token(&self) -> &Token {
match self {
Self::Action(token) | Self::Event(token, _, _) => token,
}
}
}
impl PartialEq for ActionOrEvent {
fn eq(&self, other: &Self) -> bool {
match self {
Self::Action(token) => {
if let Self::Action(other_token) = other {
token == other_token
} else {
false
}
}
Self::Event(_, namespace, nr) => {
if let Self::Event(_, other_namespace, other_nr) = other {
namespace == other_namespace && nr == other_nr
} else {
false
}
}
}
}
}
impl Eq for ActionOrEvent {}
impl Display for ActionOrEvent {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.token())
}
}
pub fn is_country_tag(part: &str) -> bool {
part.len() == 3 && part != "NOT" && part.chars().all(|c| c.is_ascii_uppercase())
}
#[cfg(feature = "hoi4")]
pub fn expand_scopes_hoi4(mut scopes: Scopes) -> Scopes {
if scopes.contains(Scopes::Country) || scopes.contains(Scopes::State) {
scopes |= Scopes::CombinedCountryAndState;
}
if scopes.contains(Scopes::Country) || scopes.contains(Scopes::Character) {
scopes |= Scopes::CombinedCountryAndCharacter;
}
scopes
}
#[inline]
pub fn snake_case_to_camel_case(s: &str) -> String {
let mut temp_s = String::with_capacity(s.len());
let mut do_uppercase = true;
for c in s.chars() {
if c == '_' {
do_uppercase = true;
} else if do_uppercase {
temp_s.push(c.to_ascii_uppercase());
do_uppercase = false;
} else {
temp_s.push(c);
}
}
temp_s
}
#[inline]
pub fn camel_case_to_separated_words(s: &str) -> String {
let mut temp_s = String::with_capacity(s.len() + 5);
for c in s.chars() {
if c.is_ascii_uppercase() {
if !temp_s.is_empty() {
temp_s.push(' ');
}
temp_s.push(c.to_ascii_lowercase());
} else {
temp_s.push(c);
}
}
temp_s
}
pub fn limited_item_prefix_should_insert<'a, 'b, F>(
itype: Item,
key: Token,
get_other: F,
) -> Option<Token>
where
F: Fn(&'a str) -> Option<&'b Token>,
{
if Game::is_vic3() || Game::is_eu5() {
#[allow(clippy::collapsible_else_if)]
#[cfg(any(feature = "vic3", feature = "eu5"))]
if let Some((prefix, name)) = key.split_once(':') {
let other = get_other(name.as_str());
match prefix.as_str() {
"INJECT" | "TRY_INJECT" | "INJECT_OR_CREATE" => {
let msg = format!("cannot inject {itype}");
err(ErrorKey::Prefixes).msg(msg).loc(prefix).push();
}
"REPLACE" => {
if other.is_some() {
return Some(name);
}
let msg = "replacing a non-existing item";
err(ErrorKey::Prefixes).msg(msg).loc(name).push();
}
"TRY_REPLACE" => {
if other.is_some() {
return Some(name);
}
}
"REPLACE_OR_CREATE" => return Some(name),
_ => {
let msg = format!("unknown prefix `{prefix}`");
err(ErrorKey::Prefixes).msg(msg).loc(prefix).push();
}
}
} else {
if let Some(other) = get_other(key.as_str()) {
let msg = format!("must have prefix such as `REPLACE:` to replace {itype}");
err(ErrorKey::Prefixes).msg(msg).loc(key).loc_msg(other, "original here").push();
} else {
return Some(key);
}
}
} else {
if let Some(other) = get_other(key.as_str())
&& other.loc.kind >= key.loc.kind
{
dup_error(&key, other, &itype.to_string());
}
return Some(key);
}
None
}
#[derive(Debug, Clone)]
#[cfg(feature = "jomini")]
pub enum PrefixShould {
Insert(Token),
#[cfg(any(feature = "vic3", feature = "eu5"))]
Inject(Token),
Ignore,
}
#[cfg(feature = "jomini")]
pub fn item_prefix_should<'a, 'b, F>(itype: Item, key: &Token, get_other: F) -> PrefixShould
where
F: Fn(&'a str) -> Option<&'b Token>,
{
if Game::is_vic3() || Game::is_eu5() {
#[allow(clippy::collapsible_else_if)]
#[cfg(any(feature = "vic3", feature = "eu5"))]
if let Some((prefix, name)) = key.split_once(':') {
let other = get_other(name.as_str());
match prefix.as_str() {
"INJECT" => {
if other.is_some() {
return PrefixShould::Inject(name);
}
let msg = "injecting into a non-existing item";
err(ErrorKey::Prefixes).msg(msg).loc(name).push();
}
"REPLACE" => {
if other.is_some() {
return PrefixShould::Insert(name);
}
let msg = "replacing a non-existing item";
err(ErrorKey::Prefixes).msg(msg).loc(name).push();
}
"TRY_INJECT" => {
if other.is_some() {
return PrefixShould::Inject(name);
}
}
"TRY_REPLACE" => {
if other.is_some() {
return PrefixShould::Insert(name);
}
}
"REPLACE_OR_CREATE" => return PrefixShould::Insert(name),
"INJECT_OR_CREATE" => {
if other.is_some() {
return PrefixShould::Inject(name);
}
return PrefixShould::Insert(name);
}
_ => {
let msg = format!("unknown prefix `{prefix}`");
err(ErrorKey::Prefixes).msg(msg).loc(prefix).push();
}
}
} else {
if let Some(other) = get_other(key.as_str()) {
let msg = format!("must have prefix such as `REPLACE:` to replace {itype}");
err(ErrorKey::Prefixes).msg(msg).loc(key).loc_msg(other, "original here").push();
} else {
return PrefixShould::Insert(key.clone());
}
}
} else {
if let Some(other) = get_other(key.as_str())
&& other.loc.kind >= key.loc.kind
{
dup_error(key, other, &itype.to_string());
}
return PrefixShould::Insert(key.clone());
}
PrefixShould::Ignore
}