1use ahash::{HashMap, HashSet, RandomState};
3use bimap::BiHashMap;
4
5use std::fmt::{Display, Formatter};
6use std::str::FromStr;
7
8use crate::report::{tips, warn, ErrorKey};
9#[cfg(feature = "hoi4")]
10use crate::scopes::Scopes;
11use crate::token::Token;
12
13pub type TigerHashMap<K, V> = HashMap<K, V>;
14pub use ahash::HashMapExt as TigerHashMapExt;
15pub type TigerHashSet<T> = HashSet<T>;
16pub use ahash::HashSetExt as TigerHashSetExt;
17
18#[macro_export]
19macro_rules! set {
20 ( $x:expr ) => {
21 ahash::AHashSet::from($x).into()
22 };
23}
24
25pub fn dup_error(key: &Token, other: &Token, id: &str) {
27 warn(ErrorKey::DuplicateItem)
28 .msg(format!("{id} is redefined by another {id}"))
29 .loc(other)
30 .loc_msg(key, format!("the other {id} is here"))
31 .push();
32}
33
34pub fn exact_dup_error(key: &Token, other: &Token, id: &str) {
36 warn(ErrorKey::ExactDuplicateItem)
37 .msg(format!("{id} is redefined by an identical {id}"))
38 .loc(other)
39 .loc_msg(key, format!("the other {id} is here"))
40 .push();
41}
42
43pub fn exact_dup_advice(key: &Token, other: &Token, id: &str) {
45 tips(ErrorKey::ExactDuplicateItem)
46 .msg(format!("{id} is redefined by an identical {id}, which may cause problems if one of them is later changed"))
47 .loc(other)
48 .loc_msg(key, format!("the other {id} is here"))
49 .push();
50}
51
52pub fn dup_assign_error(key: &Token, other: &Token) {
54 let mut key = key.clone();
57 key.loc.link_idx = None;
58 let mut other = other.clone();
59 other.loc.link_idx = None;
60
61 warn(ErrorKey::DuplicateField)
62 .msg(format!("`{other}` is redefined in a following line").as_str())
63 .loc(other.loc)
64 .loc_msg(key.loc, "the other one is here")
65 .push();
66}
67
68pub fn display_choices(f: &mut Formatter, v: &[&str], joiner: &str) -> Result<(), std::fmt::Error> {
69 for i in 0..v.len() {
70 write!(f, "{}", v[i])?;
71 if i + 1 == v.len() {
72 } else if i + 2 == v.len() {
73 write!(f, " {joiner} ")?;
74 } else {
75 write!(f, ", ")?;
76 }
77 }
78 Ok(())
79}
80
81enum Choices<'a> {
83 OrChoices(&'a [&'a str]),
84 AndChoices(&'a [&'a str]),
85}
86
87impl Display for Choices<'_> {
88 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
89 match self {
90 Choices::OrChoices(cs) => display_choices(f, cs, "or"),
91 Choices::AndChoices(cs) => display_choices(f, cs, "and"),
92 }
93 }
94}
95
96pub fn stringify_choices(v: &[&str]) -> String {
97 format!("{}", Choices::OrChoices(v))
98}
99
100pub fn stringify_list(v: &[&str]) -> String {
101 format!("{}", Choices::AndChoices(v))
102}
103
104#[derive(Copy, Clone, Debug, PartialEq, Eq)]
105#[cfg(feature = "jomini")]
106pub enum TriBool {
107 True,
108 False,
109 Maybe,
110}
111
112pub const BANNED_NAMES: &[&str] = &[
116 "if",
117 "else",
118 "else_if",
119 "trigger_if",
120 "trigger_else",
121 "trigger_else_if",
122 "while",
123 "limit",
124 "filter",
125 "switch",
126 "take_hostage", ];
128
129pub(crate) type BiTigerHashMap<L, R> = BiHashMap<L, R, RandomState, RandomState>;
130
131#[derive(Debug, Clone)]
132pub(crate) enum ActionOrEvent {
133 Action(Token),
134 Event(Token, &'static str, usize),
135}
136
137impl ActionOrEvent {
138 pub(crate) fn new_action(key: Token) -> Self {
139 Self::Action(key)
140 }
141
142 pub(crate) fn new_event(key: Token) -> Self {
143 if let Some((namespace, nr)) = key.as_str().split_once('.') {
144 if let Ok(nr) = usize::from_str(nr) {
145 return Self::Event(key, namespace, nr);
146 }
147 }
148 let namespace = key.as_str();
149 Self::Event(key, namespace, 0)
150 }
151
152 pub(crate) fn token(&self) -> &Token {
153 match self {
154 Self::Action(token) | Self::Event(token, _, _) => token,
155 }
156 }
157}
158
159impl PartialEq for ActionOrEvent {
160 fn eq(&self, other: &Self) -> bool {
161 match self {
162 Self::Action(token) => {
163 if let Self::Action(other_token) = other {
164 token == other_token
165 } else {
166 false
167 }
168 }
169 Self::Event(_, namespace, nr) => {
170 if let Self::Event(_, other_namespace, other_nr) = other {
171 namespace == other_namespace && nr == other_nr
172 } else {
173 false
174 }
175 }
176 }
177 }
178}
179
180impl Eq for ActionOrEvent {}
181
182impl Display for ActionOrEvent {
183 fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
184 write!(f, "{}", self.token())
185 }
186}
187
188pub fn is_country_tag(part: &str) -> bool {
189 part.len() == 3 && part != "NOT" && part.chars().all(|c| c.is_ascii_uppercase())
190}
191
192#[cfg(feature = "hoi4")]
193pub fn expand_scopes_hoi4(mut scopes: Scopes) -> Scopes {
194 if scopes.contains(Scopes::Country) || scopes.contains(Scopes::State) {
195 scopes |= Scopes::CombinedCountryAndState;
196 }
197 if scopes.contains(Scopes::Country) || scopes.contains(Scopes::Character) {
198 scopes |= Scopes::CombinedCountryAndCharacter;
199 }
200 scopes
201}
202
203#[inline]
204pub fn snake_case_to_camel_case(s: &str) -> String {
205 let mut temp_s = String::with_capacity(s.len());
206 let mut do_uppercase = true;
207 for c in s.chars() {
208 if c == '_' {
209 do_uppercase = true;
210 } else if do_uppercase {
211 temp_s.push(c.to_ascii_uppercase());
212 do_uppercase = false;
213 } else {
214 temp_s.push(c);
215 }
216 }
217 temp_s
218}
219
220#[inline]
221pub fn camel_case_to_separated_words(s: &str) -> String {
222 let mut temp_s = String::with_capacity(s.len() + 5);
226 for c in s.chars() {
227 if c.is_ascii_uppercase() {
228 if !temp_s.is_empty() {
229 temp_s.push(' ');
230 }
231 temp_s.push(c.to_ascii_lowercase());
232 } else {
233 temp_s.push(c);
234 }
235 }
236 temp_s
237}