1use std::borrow::Cow;
4use std::thread::panicking;
5
6use crate::everything::Everything;
7use crate::game::Game;
8use crate::helpers::{ActionOrEvent, TigerHashMap, stringify_choices};
9use crate::report::{ErrorKey, ReportBuilderFull, err, warn};
10use crate::scopes::Scopes;
11use crate::token::Token;
12
13const MAX_SCOPE_NAME_LIST: usize = 6;
15
16#[derive(Clone, Debug)]
19pub struct ScopeContext {
20 scope_stack: Vec<ScopeEntry>,
26
27 root: ScopeEntry,
29
30 #[cfg(feature = "hoi4")]
34 from: Vec<ScopeEntry>,
35
36 scope_names: TigerHashMap<&'static str, (usize, Temporary)>,
38 scope_list_names: TigerHashMap<&'static str, (usize, Temporary)>,
40 local_names: TigerHashMap<&'static str, usize>,
42 local_list_names: TigerHashMap<&'static str, usize>,
44
45 named: Vec<ScopeEntry>,
50
51 is_input: Vec<Option<Token>>,
54
55 is_builder: bool,
59
60 is_unrooted: bool,
63
64 prev_levels: usize,
68
69 strict_scopes: bool,
73
74 no_warn: bool,
78
79 source: Token,
81
82 traceback: Vec<ActionOrEvent>,
85}
86
87#[derive(Clone, Debug, Default)]
88enum ScopeEntry {
95 Backref(usize),
107
108 #[cfg(feature = "hoi4")]
111 Fromref(usize),
112
113 #[default]
116 Rootref,
117
118 Scope(Scopes, Reason),
121
122 Named(usize),
124
125 #[cfg(feature = "jomini")]
127 GlobalVar(&'static str, Reason),
128
129 #[cfg(feature = "jomini")]
131 GlobalList(&'static str, Reason),
132
133 #[cfg(feature = "jomini")]
135 Var(&'static str, Reason),
136
137 #[cfg(feature = "jomini")]
139 VarList(&'static str, Reason),
140}
141
142#[derive(Clone, Debug)]
148pub enum Reason {
149 Token(Token),
151 Name(Token),
154 Builtin(Token),
157 #[cfg(feature = "vic3")]
160 MultiplierBug(Token),
161 #[cfg(feature = "jomini")]
163 VariableReference(Token, &'static str),
164}
165
166#[repr(transparent)]
171#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
172pub struct StashedBuilder {
173 this: ScopeEntry,
174}
175
176#[derive(Clone, Debug, PartialEq, Eq, Hash)]
179pub struct Signature {
180 root: Scopes,
181 scope_names: Vec<(&'static str, Scopes)>,
183 scope_list_names: Vec<(&'static str, Scopes)>,
184 local_names: Vec<(&'static str, Scopes)>,
185 local_list_names: Vec<(&'static str, Scopes)>,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum Temporary {
190 No,
191 Yes,
192 Wiped,
193}
194
195const THIS: usize = 1;
197const PREV: usize = 2;
199
200impl Reason {
201 pub fn token(&self) -> &Token {
202 match self {
203 Reason::Token(t) | Reason::Name(t) | Reason::Builtin(t) => t,
204 #[cfg(feature = "vic3")]
205 Reason::MultiplierBug(t) => t,
206 #[cfg(feature = "jomini")]
207 Reason::VariableReference(t, _) => t,
208 }
209 }
210
211 pub fn msg(&self) -> Cow<'_, str> {
213 match self {
214 Reason::Token(t) => Cow::Owned(format!("deduced from `{t}` here")),
215 Reason::Name(_) => Cow::Borrowed("deduced from the scope's name"),
216 Reason::Builtin(_) => Cow::Borrowed("supplied by the game engine"),
217 #[cfg(feature = "vic3")]
218 Reason::MultiplierBug(_) => {
219 Cow::Borrowed("evaluated in root scope for `multiplier` (as of 1.9.8")
220 }
221 #[cfg(feature = "jomini")]
222 Reason::VariableReference(t, namespace) => {
223 Cow::Owned(format!("based on {namespace}{t}"))
224 }
225 }
226 }
227}
228
229impl ScopeEntry {
230 fn deduce<T: Into<Token>>(token: T) -> ScopeEntry {
231 let token = token.into();
232 if let Some(scopes) = scope_type_from_name(token.as_str()) {
233 ScopeEntry::Scope(scopes, Reason::Name(token))
234 } else {
235 ScopeEntry::Scope(Scopes::all(), Reason::Token(token))
236 }
237 }
238}
239
240impl ScopeContext {
241 pub fn new<T: Into<Token>>(root: Scopes, token: T) -> Self {
244 let token = token.into();
245 ScopeContext {
246 scope_stack: vec![ScopeEntry::Rootref],
247 root: ScopeEntry::Scope(root, Reason::Builtin(token.clone())),
248 #[cfg(feature = "hoi4")]
249 from: Vec::new(),
250 scope_names: TigerHashMap::default(),
251 scope_list_names: TigerHashMap::default(),
252 local_names: TigerHashMap::default(),
253 local_list_names: TigerHashMap::default(),
254 named: Vec::new(),
255 is_input: Vec::new(),
256 is_builder: false,
257 is_unrooted: false,
258 prev_levels: 0,
259 strict_scopes: true,
260 no_warn: false,
261 source: token,
262 traceback: Vec::new(),
263 }
264 }
265
266 pub fn new_unrooted<T: Into<Token>>(this: Scopes, token: T) -> Self {
272 let token = token.into();
273 ScopeContext {
274 scope_stack: vec![ScopeEntry::Scope(this, Reason::Token(token.clone()))],
275 root: ScopeEntry::Scope(Scopes::all(), Reason::Token(token.clone())),
276 #[cfg(feature = "hoi4")]
277 from: Vec::new(),
278 scope_names: TigerHashMap::default(),
279 scope_list_names: TigerHashMap::default(),
280 local_names: TigerHashMap::default(),
281 local_list_names: TigerHashMap::default(),
282 named: Vec::new(),
283 is_input: Vec::new(),
284 is_builder: false,
285 is_unrooted: true,
286 prev_levels: 0,
287 strict_scopes: true,
288 no_warn: false,
289 source: token,
290 traceback: Vec::new(),
291 }
292 }
293
294 #[cfg(feature = "hoi4")]
301 pub fn new_separate_root<T: Into<Token>>(root: Scopes, this: Scopes, token: T) -> Self {
302 let token = token.into();
303 let mut sc = ScopeContext::new(root, token.clone());
304 *sc.scope_stack.last_mut().unwrap() = ScopeEntry::Scope(this, Reason::Builtin(token));
305 sc
306 }
307
308 #[cfg(feature = "hoi4")]
309 pub fn new_with_prev<T: Into<Token>>(root: Scopes, prev: Scopes, token: T) -> Self {
310 let token = token.into();
311 let mut sc = ScopeContext::new(root, token.clone());
312 sc.scope_stack
313 .insert(sc.scope_stack.len() - 1, ScopeEntry::Scope(prev, Reason::Token(token)));
314 sc.prev_levels += 1;
315 sc
316 }
317
318 pub fn set_strict_scopes(&mut self, strict: bool) {
326 self.strict_scopes = strict;
327 }
328
329 pub fn is_strict(&self) -> bool {
332 self.strict_scopes
333 }
334
335 pub fn set_no_warn(&mut self, no_warn: bool) {
339 self.no_warn = no_warn;
340 }
341
342 pub fn set_source<T: Into<Token>>(&mut self, source: T) {
345 self.source = source.into();
346 }
347
348 fn root_for(&self, trace: ActionOrEvent, data: &Everything) -> Option<Self> {
350 if !self.strict_scopes || self.no_warn || self.traceback.contains(&trace) {
351 return None;
352 }
353 let mut new_sc = self.clone();
354 for named in &mut new_sc.named {
355 if matches!(named, ScopeEntry::Rootref) {
356 *named = new_sc.root.clone();
357 }
358 }
359 #[cfg(feature = "hoi4")]
360 new_sc.from.insert(0, new_sc.root.clone());
361 let (scopes, reason) = new_sc.scopes_reason(data);
362 new_sc.root = ScopeEntry::Scope(scopes, reason.clone());
363 new_sc.scope_stack = vec![ScopeEntry::Rootref];
364 new_sc.prev_levels = 0;
365 new_sc.is_unrooted = false;
366 new_sc.traceback.push(trace);
367 new_sc.wipe_temporaries();
368 Some(new_sc)
369 }
370
371 pub fn root_for_event<T: Into<Token>>(&self, event_id: T, data: &Everything) -> Option<Self> {
374 self.root_for(ActionOrEvent::new_event(event_id.into()), data)
375 }
376
377 pub fn root_for_action<T: Into<Token>>(&self, action: T, data: &Everything) -> Option<Self> {
380 let action = action.into();
381 if self.source == action {
382 return None;
383 }
384 self.root_for(ActionOrEvent::new_action(action), data)
385 }
386
387 pub fn wipe_temporaries(&mut self) {
388 for (_, t) in self.scope_names.values_mut() {
389 if *t == Temporary::Yes {
390 *t = Temporary::Wiped;
391 }
392 }
393 for (_, t) in self.scope_list_names.values_mut() {
394 if *t == Temporary::Yes {
395 *t = Temporary::Wiped;
396 }
397 }
398 }
399
400 #[cfg(feature = "ck3")] pub fn change_root<T: Into<Token>>(&mut self, root: Scopes, token: T) {
407 self.root = ScopeEntry::Scope(root, Reason::Builtin(token.into()));
408 }
409
410 #[doc(hidden)]
411 fn define_name_internal(
412 &mut self,
413 name: &'static str,
414 scopes: Scopes,
415 reason: Reason,
416 temp: Temporary,
417 ) {
418 if let Some((idx, t)) = self.scope_names.get_mut(name) {
419 *t = temp;
420 let idx = *idx;
421 Self::break_chains_to(&mut self.named, idx);
422 self.named[idx] = ScopeEntry::Scope(scopes, reason);
423 } else {
424 self.scope_names.insert(name, (self.named.len(), temp));
425 self.named.push(ScopeEntry::Scope(scopes, reason));
426 self.is_input.push(None);
427 }
428 }
429
430 pub fn define_name<T: Into<Token>>(&mut self, name: &'static str, scopes: Scopes, token: T) {
435 self.define_name_internal(name, scopes, Reason::Builtin(token.into()), Temporary::No);
436 }
437
438 pub fn define_name_token<T: Into<Token>>(
444 &mut self,
445 name: &'static str,
446 scopes: Scopes,
447 token: T,
448 temp: Temporary,
449 ) {
450 self.define_name_internal(name, scopes, Reason::Token(token.into()), temp);
451 }
452
453 pub fn is_name_defined(&mut self, name: &str, data: &Everything) -> Option<Scopes> {
457 if let Some(&(idx, temp)) = self.scope_names.get(name) {
458 if temp == Temporary::Wiped {
459 None
460 } else {
461 #[allow(clippy::indexing_slicing)] Some(match self.named[idx] {
463 ScopeEntry::Scope(s, _) => s,
464 ScopeEntry::Backref(_) => unreachable!(),
465 #[cfg(feature = "hoi4")]
466 ScopeEntry::Fromref(_) => unreachable!(),
467 ScopeEntry::Rootref => self.resolve_root().0,
468 ScopeEntry::Named(idx) => self.resolve_named(idx, data).0,
469 #[cfg(feature = "jomini")]
470 ScopeEntry::GlobalVar(name, _) => data.global_scopes.scopes(name),
471 #[cfg(feature = "jomini")]
472 ScopeEntry::GlobalList(name, _) => data.global_list_scopes.scopes(name),
473 #[cfg(feature = "jomini")]
474 ScopeEntry::Var(name, _) => data.variable_scopes.scopes(name),
475 #[cfg(feature = "jomini")]
476 ScopeEntry::VarList(name, _) => data.variable_list_scopes.scopes(name),
477 })
478 }
479 } else {
480 None
481 }
482 }
483
484 #[cfg(feature = "hoi4")]
487 pub fn push_as_from<T: Into<Token>>(&mut self, scopes: Scopes, token: T) {
488 self.from.insert(0, ScopeEntry::Scope(scopes, Reason::Builtin(token.into())));
489 }
490
491 pub fn exists_scope<T: Into<Token>>(&mut self, name: &'static str, token: T) {
500 if !self.scope_names.contains_key(name) {
501 let idx = self.named.len();
502 self.scope_names.insert(name, (idx, Temporary::No));
503 self.named.push(ScopeEntry::deduce(token));
504 self.is_input.push(None);
505 }
506 }
507
508 pub fn exists_local<T: Into<Token>>(&mut self, name: &'static str, token: T) {
513 if !self.local_names.contains_key(name) {
514 self.local_names.insert(name, self.named.len());
515 self.named.push(ScopeEntry::deduce(token));
516 self.is_input.push(None);
517 }
518 }
519
520 pub fn exists_local_list<T: Into<Token>>(&mut self, name: &'static str, token: T) {
525 if !self.local_list_names.contains_key(name) {
526 self.local_list_names.insert(name, self.named.len());
527 self.named.push(ScopeEntry::deduce(token));
528 self.is_input.push(None);
529 }
530 }
531
532 #[doc(hidden)]
533 fn define_list_internal(
534 &mut self,
535 name: &'static str,
536 scopes: Scopes,
537 reason: Reason,
538 temp: Temporary,
539 ) {
540 if let Some(&(idx, _)) = self.scope_list_names.get(name) {
541 Self::break_chains_to(&mut self.named, idx);
542 self.named[idx] = ScopeEntry::Scope(scopes, reason);
543 } else {
544 self.scope_list_names.insert(name, (self.named.len(), temp));
545 self.named.push(ScopeEntry::Scope(scopes, reason));
546 self.is_input.push(None);
547 }
548 }
549
550 pub fn define_list<T: Into<Token>>(&mut self, name: &'static str, scopes: Scopes, token: T) {
559 self.define_list_internal(name, scopes, Reason::Builtin(token.into()), Temporary::No);
560 }
561
562 pub fn save_current_scope(&mut self, name: &'static str, temp: Temporary) {
564 if let Some((idx, t)) = self.scope_names.get_mut(name) {
565 *t = temp;
566 let idx = *idx;
567 Self::break_chains_to(&mut self.named, idx);
568 let entry = self.resolve_backrefs(THIS);
569 if let ScopeEntry::Named(i) = entry {
571 if *i == idx {
572 return;
574 }
575 }
576 self.named[idx] = entry.clone();
577 } else {
578 self.scope_names.insert(name, (self.named.len(), temp));
579 self.named.push(self.resolve_backrefs(THIS).clone());
580 self.is_input.push(None);
581 }
582 }
583
584 #[cfg(feature = "jomini")]
586 pub fn set_local_variable(&mut self, name: &Token, scope: Scopes) {
587 if let Some(&idx) = self.local_names.get(name.as_str()) {
588 Self::break_chains_to(&mut self.named, idx);
589 self.named[idx] = ScopeEntry::Scope(scope, Reason::Token(name.clone()));
590 } else {
591 self.local_names.insert(name.as_str(), self.named.len());
592 self.named.push(ScopeEntry::Scope(scope, Reason::Token(name.clone())));
593 self.is_input.push(None);
594 }
595 }
596
597 pub fn define_or_expect_list_this(&mut self, name: &Token, data: &Everything, temp: Temporary) {
601 if let Some((idx, t)) = self.scope_list_names.get_mut(name.as_str()) {
602 *t = temp;
603 let idx = *idx;
604 let (s, reason) = self.resolve_named(idx, data);
606 self.expect(s, &reason.clone(), data);
607 let (s, reason) = self.scopes_reason(data);
608 let reason = reason.clone();
609 self.expect_named(idx, s, &reason, data);
610 self.is_input[idx] = None;
614 } else {
615 self.scope_list_names.insert(name.as_str(), (self.named.len(), temp));
616 self.named.push(self.resolve_backrefs(THIS).clone());
617 self.is_input.push(None);
618 }
619 }
620
621 #[allow(dead_code)]
624 pub fn define_or_expect_list(
625 &mut self,
626 name: &Token,
627 scope: Scopes,
628 data: &Everything,
629 temp: Temporary,
630 ) {
631 if let Some((idx, t)) = self.scope_list_names.get_mut(name.as_str()) {
632 *t = temp;
633 let idx = *idx;
634 self.expect_named(idx, scope, &Reason::Token(name.clone()), data);
635 self.is_input[idx] = None;
639 } else {
640 self.scope_list_names.insert(name.as_str(), (self.named.len(), temp));
641 self.named.push(ScopeEntry::Scope(scope, Reason::Token(name.clone())));
642 self.is_input.push(None);
643 }
644 }
645
646 pub fn define_or_expect_local_list(&mut self, name: &Token, scope: Scopes, data: &Everything) {
649 if let Some(&idx) = self.local_list_names.get(name.as_str()) {
650 self.expect_named(idx, scope, &Reason::Token(name.clone()), data);
651 self.is_input[idx] = None;
655 } else {
656 self.local_list_names.insert(name.as_str(), self.named.len());
657 self.named.push(ScopeEntry::Scope(scope, Reason::Token(name.clone())));
658 self.is_input.push(None);
659 }
660 }
661
662 pub fn expect_list(&mut self, name: &Token, data: &Everything) {
665 if let Some((idx, t)) = self.scope_list_names.get(name.as_str()) {
666 if *t == Temporary::Wiped {
667 let msg = format!("list `{name}` was temporary and is no longer available here");
668 err(ErrorKey::TemporaryScope).weak().msg(msg).loc(name).push();
669 } else {
670 let (s, reason) = self.resolve_named(*idx, data);
671 let reason = reason.clone(); self.expect3(s, &reason, name, THIS, "list", data);
673 }
674 } else if self.strict_scopes {
675 let msg = "unknown list";
676 err(ErrorKey::UnknownList).weak().msg(msg).loc(name).push();
677 }
678 }
679
680 #[cfg(feature = "jomini")]
683 pub fn expect_local_list(&mut self, name: &Token, data: &Everything) {
684 if let Some(&idx) = self.local_list_names.get(name.as_str()) {
685 let (s, reason) = self.resolve_named(idx, data);
686 let reason = reason.clone(); self.expect3(s, &reason, name, THIS, "local variable list", data);
688 } else if self.strict_scopes {
689 let msg = "unknown local variable list";
690 err(ErrorKey::UnknownList).weak().msg(msg).loc(name).push();
691 }
692 }
693
694 #[cfg(feature = "jomini")]
696 pub fn expect_local(&mut self, name: &Token, scope: Scopes, data: &Everything) {
697 if let Some(&idx) = self.local_names.get(name.as_str()) {
698 self.expect_named(idx, scope, &Reason::Token(name.clone()), data);
699 } else if self.strict_scopes {
700 let msg = "unknown local variable";
701 err(ErrorKey::UnknownVariable).msg(msg).loc(name).push();
702 }
703 }
704
705 #[doc(hidden)]
707 fn break_chains_to(named: &mut [ScopeEntry], idx: usize) {
708 for i in 0..named.len() {
709 if i == idx {
710 continue;
711 }
712 if let ScopeEntry::Named(ni) = named[i] {
713 if ni == idx {
714 named[i] = named[idx].clone();
715 }
716 }
717 }
718 }
719
720 pub fn open_scope(&mut self, scopes: Scopes, token: Token) {
725 self.scope_stack.push(ScopeEntry::Scope(scopes, Reason::Token(token)));
726 }
727
728 pub fn open_builder(&mut self) {
735 self.scope_stack.push(ScopeEntry::Backref(THIS));
736 self.is_builder = true;
737 }
738
739 #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
740 pub fn stash_builder(&mut self) -> StashedBuilder {
741 let stash = StashedBuilder { this: self.scope_stack.pop().unwrap() };
742 self.is_builder = false;
743 stash
744 }
745
746 #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
747 pub fn unstash_builder(&mut self, stash: StashedBuilder) {
748 self.scope_stack.push(stash.this);
749 self.is_builder = true;
750 }
751
752 pub fn finalize_builder(&mut self) {
754 self.is_builder = false;
755 }
756
757 pub fn close(&mut self) {
759 self.scope_stack.pop().expect("matching open and close scopes");
760 self.is_builder = false;
761 }
762
763 pub fn signature(&self, data: &Everything) -> Signature {
766 fn process_scope_names(
767 sc: &ScopeContext,
768 names: &TigerHashMap<&'static str, (usize, Temporary)>,
769 data: &Everything,
770 ) -> Vec<(&'static str, Scopes)> {
771 let mut names: Vec<_> = names
772 .iter()
773 .filter(|(_, (_, temp))| *temp != Temporary::Wiped)
774 .map(|(&name, (i, _))| (name, sc.resolve_named(*i, data).0))
775 .collect();
776 names.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
777 names
778 }
779
780 fn process_local_names(
781 sc: &ScopeContext,
782 names: &TigerHashMap<&'static str, usize>,
783 data: &Everything,
784 ) -> Vec<(&'static str, Scopes)> {
785 let mut names: Vec<_> =
786 names.iter().map(|(&name, &i)| (name, sc.resolve_named(i, data).0)).collect();
787 names.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
788 names
789 }
790
791 let root = self.resolve_root().0;
792
793 Signature {
794 root,
795 scope_names: process_scope_names(self, &self.scope_names, data),
796 scope_list_names: process_scope_names(self, &self.scope_list_names, data),
797 local_names: process_local_names(self, &self.local_names, data),
798 local_list_names: process_local_names(self, &self.local_list_names, data),
799 }
800 }
801
802 pub fn replace(&mut self, scopes: Scopes, token: Token) {
807 *self.scope_stack.last_mut().unwrap() = ScopeEntry::Scope(scopes, Reason::Token(token));
808 }
809
810 pub fn replace_root(&mut self) {
812 *self.scope_stack.last_mut().unwrap() = ScopeEntry::Rootref;
813 }
814
815 pub fn replace_prev(&mut self) {
817 let this = self.scope_stack.last_mut().unwrap();
818 let backref = if Game::is_imperator() || Game::is_hoi4() {
819 match this {
821 ScopeEntry::Backref(r) => *r + 1,
822 _ => PREV,
823 }
824 } else {
825 PREV
826 };
827 *this = ScopeEntry::Backref(backref);
828 while 1 + backref > self.scope_stack.len() {
829 let entry = if self.is_unrooted {
831 ScopeEntry::Scope(Scopes::all(), Reason::Token(self.source.clone()))
832 } else {
833 ScopeEntry::Scope(Scopes::None, Reason::Builtin(self.source.clone()))
834 };
835 self.scope_stack.insert(0, entry);
836 self.prev_levels += 1;
837 }
838 }
839
840 #[cfg(feature = "hoi4")]
842 pub fn replace_from(&mut self) {
843 let this = self.scope_stack.last_mut().unwrap();
844 match this {
845 ScopeEntry::Fromref(r) => *this = ScopeEntry::Fromref(*r + 1),
846 _ => *this = ScopeEntry::Fromref(0),
847 }
848 }
849
850 pub fn replace_this(&mut self) {
856 *self.scope_stack.last_mut().unwrap() = ScopeEntry::Backref(THIS);
857 }
858
859 pub fn replace_named_scope(&mut self, name: &'static str, token: &Token) {
864 *self.scope_stack.last_mut().unwrap() = ScopeEntry::Named(self.named_index(name, token));
865 }
866
867 pub fn replace_local_variable(&mut self, name: &'static str, token: &Token) {
872 *self.scope_stack.last_mut().unwrap() = ScopeEntry::Named(self.local_index(name, token));
873 }
874
875 #[cfg(feature = "jomini")]
880 pub fn replace_global_variable(&mut self, name: &'static str, token: &Token) {
881 *self.scope_stack.last_mut().unwrap() =
882 ScopeEntry::GlobalVar(name, Reason::VariableReference(token.clone(), "global_var:"));
883 }
884
885 #[cfg(feature = "jomini")]
890 pub fn replace_variable(&mut self, name: &'static str, token: &Token) {
891 *self.scope_stack.last_mut().unwrap() =
892 ScopeEntry::Var(name, Reason::VariableReference(token.clone(), "var:"));
893 }
894
895 #[cfg(feature = "jomini")]
898 pub fn replace_list_entry(&mut self, name: &Token) {
899 *self.scope_stack.last_mut().unwrap() =
900 ScopeEntry::Named(self.named_list_index(name.as_str(), name));
901 }
902
903 #[cfg(feature = "jomini")]
906 pub fn replace_local_list_entry(&mut self, name: &Token) {
907 *self.scope_stack.last_mut().unwrap() =
908 ScopeEntry::Named(self.local_list_index(name.as_str(), name));
909 }
910
911 #[cfg(feature = "jomini")]
914 pub fn replace_global_list_entry(&mut self, name: &Token) {
915 *self.scope_stack.last_mut().unwrap() = ScopeEntry::GlobalList(
916 name.as_str(),
917 Reason::VariableReference(name.clone(), "global list "),
918 );
919 }
920
921 #[cfg(feature = "jomini")]
924 pub fn replace_variable_list_entry(&mut self, name: &Token) {
925 *self.scope_stack.last_mut().unwrap() = ScopeEntry::VarList(
926 name.as_str(),
927 Reason::VariableReference(name.clone(), "variable list "),
928 );
929 }
930
931 #[doc(hidden)]
935 fn named_index(&mut self, name: &'static str, token: &Token) -> usize {
936 if let Some(&(idx, t)) = self.scope_names.get(name) {
937 if t == Temporary::Wiped {
938 let msg = format!("`scope:{name}` was temporary and is no longer available here");
939 self.log_traceback(err(ErrorKey::TemporaryScope).weak().msg(msg).loc(token)).push();
940 }
941 idx
942 } else {
943 let idx = self.named.len();
944 self.named.push(ScopeEntry::deduce(token));
945 if self.strict_scopes {
946 if !self.no_warn {
947 let msg = format!("scope:{name} might not be available here");
948 let mut builder = err(ErrorKey::StrictScopes).weak().msg(msg);
949 if self.scope_names.len() <= MAX_SCOPE_NAME_LIST && !self.scope_names.is_empty()
950 {
951 let mut names: Vec<_> = self.scope_names.keys().copied().collect();
952 names.sort_unstable();
953 let info = format!("available names are {}", stringify_choices(&names));
954 builder = builder.info(info);
955 }
956 self.log_traceback(builder.loc(token)).push();
957 }
958 self.is_input.push(None);
960 } else {
961 self.is_input.push(Some(token.clone()));
962 }
963 self.scope_names.insert(name, (idx, Temporary::No));
965 idx
966 }
967 }
968
969 #[doc(hidden)]
973 fn local_index(&mut self, name: &'static str, token: &Token) -> usize {
974 if let Some(&idx) = self.local_names.get(name) {
975 idx
976 } else {
977 let idx = self.named.len();
978 self.named.push(ScopeEntry::deduce(token));
979 if self.strict_scopes {
980 if !self.no_warn {
981 let msg = format!("local_var:{name} might not be available here");
982 let mut builder = err(ErrorKey::StrictScopes).weak().msg(msg);
983 if self.local_names.len() <= MAX_SCOPE_NAME_LIST && !self.local_names.is_empty()
984 {
985 let mut names: Vec<_> = self.local_names.keys().copied().collect();
986 names.sort_unstable();
987 let info = format!("available names are {}", stringify_choices(&names));
988 builder = builder.info(info);
989 }
990 self.log_traceback(builder.loc(token)).push();
991 }
992 self.is_input.push(None);
994 } else {
995 self.is_input.push(Some(token.clone()));
996 }
997 self.local_names.insert(name, idx);
999 idx
1000 }
1001 }
1002
1003 #[doc(hidden)]
1006 #[cfg(feature = "jomini")]
1007 fn named_list_index(&mut self, name: &'static str, token: &Token) -> usize {
1008 if let Some(&(idx, t)) = self.scope_list_names.get(name) {
1009 if t == Temporary::Wiped {
1010 let msg = format!("list `{name}` was temporary and is no longer available here");
1011 self.log_traceback(err(ErrorKey::TemporaryScope).weak().msg(msg).loc(token)).push();
1012 }
1013 idx
1014 } else {
1015 let idx = self.named.len();
1016 self.scope_list_names.insert(name, (idx, Temporary::No));
1017 self.named.push(ScopeEntry::Scope(Scopes::all(), Reason::Token(token.clone())));
1018 self.is_input.push(Some(token.clone()));
1019 idx
1020 }
1021 }
1022
1023 #[doc(hidden)]
1026 #[cfg(feature = "jomini")]
1027 fn local_list_index(&mut self, name: &'static str, token: &Token) -> usize {
1028 if let Some(&idx) = self.local_list_names.get(name) {
1029 idx
1030 } else {
1031 let idx = self.named.len();
1032 self.local_list_names.insert(name, idx);
1033 self.named.push(ScopeEntry::Scope(Scopes::all(), Reason::Token(token.clone())));
1034 self.is_input.push(Some(token.clone()));
1035 idx
1036 }
1037 }
1038
1039 pub fn can_be(&self, scopes: Scopes, data: &Everything) -> bool {
1041 self.scopes(data).intersects(scopes)
1042 }
1043
1044 pub fn scopes(&self, data: &Everything) -> Scopes {
1047 self.scopes_reason(data).0
1048 }
1049
1050 #[cfg(feature = "jomini")]
1052 pub fn local_variable_scopes(&self, name: &str, data: &Everything) -> Scopes {
1053 if let Some(idx) = self.local_names.get(name).copied() {
1054 self.resolve_named(idx, data).0
1055 } else {
1056 Scopes::all_but_none()
1057 }
1058 }
1059
1060 pub fn local_list_scopes(&self, name: &str, data: &Everything) -> Scopes {
1062 if let Some(idx) = self.local_list_names.get(name).copied() {
1063 self.resolve_named(idx, data).0
1064 } else {
1065 Scopes::all_but_none()
1066 }
1067 }
1068
1069 #[cfg(feature = "vic3")]
1070 pub fn get_multiplier_context(&self, key: &Token) -> Self {
1071 let scopes = self.resolve_root().0;
1072 let mut sc = ScopeContext::new(scopes, key);
1073 sc.root = ScopeEntry::Scope(scopes, Reason::MultiplierBug(key.clone()));
1074 sc
1075 }
1076
1077 fn resolve_root(&self) -> (Scopes, &Reason) {
1079 match self.root {
1080 ScopeEntry::Scope(s, ref reason) => (s, reason),
1081 _ => unreachable!(),
1082 }
1083 }
1084
1085 #[doc(hidden)]
1090 #[allow(clippy::only_used_in_recursion)] fn resolve_named(&self, idx: usize, data: &Everything) -> (Scopes, &Reason) {
1092 #[allow(clippy::indexing_slicing)]
1093 match self.named[idx] {
1094 ScopeEntry::Scope(s, ref reason) => (s, reason),
1095 ScopeEntry::Rootref => self.resolve_root(),
1096 ScopeEntry::Named(idx) => self.resolve_named(idx, data),
1097 ScopeEntry::Backref(_) => unreachable!(),
1098 #[cfg(feature = "hoi4")]
1099 ScopeEntry::Fromref(_) => unreachable!(),
1100 #[cfg(feature = "jomini")]
1101 ScopeEntry::GlobalVar(name, ref reason) => (data.global_scopes.scopes(name), reason),
1102 #[cfg(feature = "jomini")]
1103 ScopeEntry::GlobalList(name, ref reason) => {
1104 (data.global_list_scopes.scopes(name), reason)
1105 }
1106 #[cfg(feature = "jomini")]
1107 ScopeEntry::Var(name, ref reason) => (data.variable_scopes.scopes(name), reason),
1108 #[cfg(feature = "jomini")]
1109 ScopeEntry::VarList(name, ref reason) => {
1110 (data.variable_list_scopes.scopes(name), reason)
1111 }
1112 }
1113 }
1114
1115 #[doc(hidden)]
1120 fn resolve_backrefs(&self, mut back: usize) -> &ScopeEntry {
1121 loop {
1122 assert!(back > 0);
1125 assert!(back <= self.scope_stack.len());
1126 let entry = &self.scope_stack[self.scope_stack.len() - back];
1127 match entry {
1128 ScopeEntry::Backref(r) => back += *r,
1129 _ => {
1130 return entry;
1131 }
1132 }
1133 }
1134 }
1135
1136 #[doc(hidden)]
1141 fn resolve_backrefs_mut(&mut self, mut back: usize) -> &mut ScopeEntry {
1142 loop {
1144 assert!(back > 0);
1147 assert!(back <= self.scope_stack.len());
1148 let idx = self.scope_stack.len() - back;
1149 match self.scope_stack[idx] {
1150 ScopeEntry::Backref(r) => back += r,
1151 _ => {
1152 return &mut self.scope_stack[idx];
1153 }
1154 }
1155 }
1156 }
1157
1158 pub fn scopes_reason(&self, data: &Everything) -> (Scopes, &Reason) {
1161 self.scopes_reason_backref(THIS, data)
1162 }
1163
1164 #[cfg(feature = "hoi4")]
1167 #[doc(hidden)]
1168 fn resolve_from(&self, back: usize) -> (Scopes, &Reason) {
1169 match self.from.get(back) {
1170 None => {
1171 let scopes = if self.strict_scopes { Scopes::None } else { Scopes::all() };
1173 match self.root {
1175 ScopeEntry::Scope(_, ref reason) => (scopes, reason),
1176 _ => unreachable!(),
1177 }
1178 }
1179 Some(ScopeEntry::Scope(s, reason)) => (*s, reason),
1180 Some(_) => unreachable!(),
1181 }
1182 }
1183
1184 #[doc(hidden)]
1185 fn scopes_reason_backref(&self, back: usize, data: &Everything) -> (Scopes, &Reason) {
1186 match self.resolve_backrefs(back) {
1187 ScopeEntry::Scope(s, reason) => (*s, reason),
1188 ScopeEntry::Backref(_) => unreachable!(),
1189 #[cfg(feature = "hoi4")]
1190 ScopeEntry::Fromref(r) => self.resolve_from(*r),
1191 ScopeEntry::Rootref => self.resolve_root(),
1192 ScopeEntry::Named(idx) => self.resolve_named(*idx, data),
1193 #[cfg(feature = "jomini")]
1194 ScopeEntry::GlobalVar(name, reason) => (data.global_scopes.scopes(name), reason),
1195 #[cfg(feature = "jomini")]
1196 ScopeEntry::GlobalList(name, reason) => (data.global_list_scopes.scopes(name), reason),
1197 #[cfg(feature = "jomini")]
1198 ScopeEntry::Var(name, reason) => (data.variable_scopes.scopes(name), reason),
1199 #[cfg(feature = "jomini")]
1200 ScopeEntry::VarList(name, reason) => (data.variable_list_scopes.scopes(name), reason),
1201 }
1202 }
1203
1204 pub fn log_traceback(&self, mut builder: ReportBuilderFull) -> ReportBuilderFull {
1206 for elem in self.traceback.iter().rev() {
1207 builder = builder.loc_msg(elem.token(), "triggered from here");
1208 }
1209 builder.loc_msg(&self.source, "scopes initialized here")
1210 }
1211
1212 #[doc(hidden)]
1213 #[allow(unused_variables)] fn expect_check(e: &mut ScopeEntry, scopes: Scopes, reason: &Reason, data: &Everything) {
1215 match e {
1216 ScopeEntry::Scope(s, r) => {
1217 if s.intersects(scopes) {
1218 if (*s & scopes) != *s {
1220 *s &= scopes;
1221 *r = reason.clone();
1222 }
1223 } else {
1224 let token = reason.token();
1225 let msg = format!("`{token}` is for {scopes} but scope seems to be {s}");
1226 let msg2 = format!("scope was {}", r.msg());
1227 warn(ErrorKey::Scopes).msg(msg).loc(token).loc_msg(r.token(), msg2).push();
1228 }
1229 }
1230 #[cfg(feature = "jomini")]
1231 ScopeEntry::GlobalVar(name, reason) => {
1232 data.global_scopes.expect(name, reason.token(), scopes);
1233 }
1234 #[cfg(feature = "jomini")]
1235 ScopeEntry::GlobalList(name, reason) => {
1236 data.global_list_scopes.expect(name, reason.token(), scopes);
1237 }
1238 #[cfg(feature = "jomini")]
1239 ScopeEntry::Var(name, reason) => {
1240 data.variable_scopes.expect(name, reason.token(), scopes);
1241 }
1242 #[cfg(feature = "jomini")]
1243 ScopeEntry::VarList(name, reason) => {
1244 data.variable_list_scopes.expect(name, reason.token(), scopes);
1245 }
1246 _ => unreachable!(),
1247 }
1248 }
1249
1250 #[doc(hidden)]
1251 #[allow(unused_variables)] fn expect_check3(
1253 e: &mut ScopeEntry,
1254 scopes: Scopes,
1255 reason: &Reason,
1256 key: &Token,
1257 report: &str,
1258 data: &Everything,
1259 ) {
1260 match e {
1261 ScopeEntry::Scope(s, r) => {
1262 if s.intersects(scopes) {
1263 if (*s & scopes) != *s {
1265 *s &= scopes;
1266 *r = reason.clone();
1267 }
1268 } else {
1269 let msg = format!(
1270 "`{key}` expects {report} to be {scopes} but {report} seems to be {s}"
1271 );
1272 let msg2 = format!("expected {report} was {}", reason.msg());
1273 let msg3 = format!("actual {report} was {}", r.msg());
1274 warn(ErrorKey::Scopes)
1275 .msg(msg)
1276 .loc(key)
1277 .loc_msg(reason.token(), msg2)
1278 .loc_msg(r.token(), msg3)
1279 .push();
1280 }
1281 }
1282 #[cfg(feature = "jomini")]
1283 ScopeEntry::GlobalVar(name, reason) => {
1284 data.global_scopes.expect(name, reason.token(), scopes);
1285 }
1286 #[cfg(feature = "jomini")]
1287 ScopeEntry::GlobalList(name, reason) => {
1288 data.global_list_scopes.expect(name, reason.token(), scopes);
1289 }
1290 #[cfg(feature = "jomini")]
1291 ScopeEntry::Var(name, reason) => {
1292 data.variable_scopes.expect(name, reason.token(), scopes);
1293 }
1294 #[cfg(feature = "jomini")]
1295 ScopeEntry::VarList(name, reason) => {
1296 data.variable_list_scopes.expect(name, reason.token(), scopes);
1297 }
1298 _ => unreachable!(),
1299 }
1300 }
1301
1302 #[doc(hidden)]
1303 #[cfg(feature = "hoi4")]
1304 fn expect_fromref(&mut self, idx: usize, scopes: Scopes, reason: &Reason, data: &Everything) {
1305 if idx < self.from.len() {
1306 Self::expect_check(&mut self.from[idx], scopes, reason, data);
1307 }
1308 }
1309
1310 #[doc(hidden)]
1311 #[cfg(feature = "hoi4")]
1312 fn expect_fromref3(
1313 &mut self,
1314 idx: usize,
1315 scopes: Scopes,
1316 reason: &Reason,
1317 key: &Token,
1318 report: &str,
1319 data: &Everything,
1320 ) {
1321 if idx < self.from.len() {
1322 Self::expect_check3(&mut self.from[idx], scopes, reason, key, report, data);
1323 }
1324 }
1325
1326 #[doc(hidden)]
1328 fn expect_named(&mut self, mut idx: usize, scopes: Scopes, reason: &Reason, data: &Everything) {
1329 loop {
1330 #[allow(clippy::indexing_slicing)]
1331 match self.named[idx] {
1332 ScopeEntry::Scope(_, _) => {
1333 Self::expect_check(&mut self.named[idx], scopes, reason, data);
1334 return;
1335 }
1336 ScopeEntry::Rootref => {
1337 Self::expect_check(&mut self.root, scopes, reason, data);
1338 return;
1339 }
1340 ScopeEntry::Named(i) => idx = i,
1341 ScopeEntry::Backref(_) => unreachable!(),
1342 #[cfg(feature = "hoi4")]
1343 ScopeEntry::Fromref(_) => unreachable!(),
1344 #[cfg(feature = "jomini")]
1345 ScopeEntry::GlobalVar(_, _)
1346 | ScopeEntry::GlobalList(_, _)
1347 | ScopeEntry::Var(_, _)
1348 | ScopeEntry::VarList(_, _) => {
1349 Self::expect_check(&mut self.named[idx], scopes, reason, data);
1350 return;
1351 }
1352 }
1353 }
1354 }
1355
1356 #[doc(hidden)]
1357 fn expect_named3(
1358 &mut self,
1359 mut idx: usize,
1360 scopes: Scopes,
1361 reason: &Reason,
1362 key: &Token,
1363 report: &str,
1364 data: &Everything,
1365 ) {
1366 loop {
1367 #[allow(clippy::indexing_slicing)]
1368 match self.named[idx] {
1369 ScopeEntry::Scope(_, _) => {
1370 Self::expect_check3(&mut self.named[idx], scopes, reason, key, report, data);
1371 return;
1372 }
1373 ScopeEntry::Rootref => {
1374 Self::expect_check3(&mut self.root, scopes, reason, key, report, data);
1375 return;
1376 }
1377 ScopeEntry::Named(i) => idx = i,
1378 ScopeEntry::Backref(_) => unreachable!(),
1379 #[cfg(feature = "hoi4")]
1380 ScopeEntry::Fromref(_) => unreachable!(),
1381 #[cfg(feature = "jomini")]
1382 ScopeEntry::GlobalVar(_, _)
1383 | ScopeEntry::GlobalList(_, _)
1384 | ScopeEntry::Var(_, _)
1385 | ScopeEntry::VarList(_, _) => {
1386 Self::expect_check3(&mut self.named[idx], scopes, reason, key, report, data);
1387 return;
1388 }
1389 }
1390 }
1391 }
1392
1393 pub fn expect(&mut self, scopes: Scopes, reason: &Reason, data: &Everything) {
1397 if self.no_warn || scopes == Scopes::None {
1399 return;
1400 }
1401 let this = self.resolve_backrefs_mut(THIS);
1402 match this {
1403 ScopeEntry::Scope(_, _) => Self::expect_check(this, scopes, reason, data),
1404 ScopeEntry::Backref(_) => unreachable!(),
1405 #[cfg(feature = "hoi4")]
1406 &mut ScopeEntry::Fromref(r) => self.expect_fromref(r, scopes, reason, data),
1407 ScopeEntry::Rootref => Self::expect_check(&mut self.root, scopes, reason, data),
1408 &mut ScopeEntry::Named(idx) => self.expect_named(idx, scopes, reason, data),
1409 #[cfg(feature = "jomini")]
1410 ScopeEntry::GlobalVar(_, _)
1411 | ScopeEntry::GlobalList(_, _)
1412 | ScopeEntry::Var(_, _)
1413 | ScopeEntry::VarList(_, _) => Self::expect_check(this, scopes, reason, data),
1414 }
1415 }
1416
1417 fn expect3(
1422 &mut self,
1423 scopes: Scopes,
1424 reason: &Reason,
1425 key: &Token,
1426 back: usize,
1427 report: &str,
1428 data: &Everything,
1429 ) {
1430 if scopes == Scopes::None {
1432 return;
1433 }
1434 let this = self.resolve_backrefs_mut(back);
1435 match this {
1436 ScopeEntry::Scope(_, _) => Self::expect_check3(this, scopes, reason, key, report, data),
1437 ScopeEntry::Backref(_) => unreachable!(),
1438 #[cfg(feature = "hoi4")]
1439 &mut ScopeEntry::Fromref(r) => {
1440 self.expect_fromref3(r, scopes, reason, key, report, data);
1441 }
1442 ScopeEntry::Rootref => {
1443 Self::expect_check3(&mut self.root, scopes, reason, key, report, data);
1444 }
1445 &mut ScopeEntry::Named(idx) => {
1446 self.expect_named3(idx, scopes, reason, key, report, data);
1447 }
1448 #[cfg(feature = "jomini")]
1449 ScopeEntry::GlobalVar(_, _)
1450 | ScopeEntry::GlobalList(_, _)
1451 | ScopeEntry::Var(_, _)
1452 | ScopeEntry::VarList(_, _) => {
1453 Self::expect_check3(this, scopes, reason, key, report, data);
1454 }
1455 }
1456 }
1457
1458 pub fn expect_compatibility(&mut self, other: &ScopeContext, key: &Token, data: &Everything) {
1465 if self.no_warn {
1466 return;
1467 }
1468 match other.root {
1470 ScopeEntry::Scope(scopes, ref token) => {
1471 Self::expect_check3(&mut self.root, scopes, token, key, "root", data);
1472 }
1473 _ => unreachable!(),
1474 }
1475
1476 let (scopes, reason) = other.scopes_reason(data);
1478 self.expect3(scopes, reason, key, THIS, "scope", data);
1479
1480 if other.prev_levels > 0 && self.prev_levels > 0 {
1484 let (scopes, reason) = other.scopes_reason_backref(PREV, data);
1485 self.expect3(scopes, reason, key, PREV + usize::from(self.is_builder), "prev", data);
1486 }
1487
1488 #[cfg(feature = "hoi4")]
1491 {
1492 let (scopes, reason) = other.resolve_from(0);
1493 self.expect_fromref3(0, scopes, reason, key, "from", data);
1494 let (scopes, reason) = other.resolve_from(1);
1495 self.expect_fromref3(1, scopes, reason, key, "from.from", data);
1496 }
1497
1498 for (name, &(oidx, otemp)) in &other.scope_names {
1500 if let Some((idx, t)) = self.scope_names.get(name).copied() {
1501 let (s, reason) = other.resolve_named(oidx, data);
1502 if other.is_input[oidx].is_some() {
1503 let report = format!("scope:{name}");
1504 if t == Temporary::Wiped {
1505 let msg = format!(
1506 "`{key}` expects {report} to be set but {report} was temporary and is no longer available here"
1507 );
1508 err(ErrorKey::TemporaryScope).weak().msg(msg).loc(key).push();
1509 }
1510 self.expect_named3(idx, s, reason, key, &report, data);
1511 } else {
1512 self.scope_names.get_mut(name).unwrap().1 = otemp;
1514 Self::break_chains_to(&mut self.named, idx);
1515 self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1516 }
1517 } else if self.strict_scopes && other.is_input[oidx].is_some() {
1518 let token = other.is_input[oidx].as_ref().unwrap();
1519 let msg = format!("`{key}` expects scope:{name} to be set");
1520 let msg2 = "here";
1521 self.log_traceback(
1522 warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1523 )
1524 .push();
1525 } else {
1526 let (s, reason) = other.resolve_named(oidx, data);
1528 self.scope_names.insert(name, (self.named.len(), otemp));
1529 self.named.push(ScopeEntry::Scope(s, reason.clone()));
1530 self.is_input.push(other.is_input[oidx].clone());
1531 }
1532 }
1533
1534 for (name, &(oidx, otemp)) in &other.scope_list_names {
1536 if let Some((idx, t)) = self.scope_list_names.get(name).copied() {
1537 let (s, reason) = other.resolve_named(oidx, data);
1538 if other.is_input[oidx].is_some() {
1539 let report = format!("list {name}");
1540 if t == Temporary::Wiped {
1541 let msg = format!(
1542 "`{key}` expects {report} to be set but {report} was temporary and is no longer available here"
1543 );
1544 err(ErrorKey::TemporaryScope).weak().msg(msg).loc(key).push();
1545 }
1546 self.expect_named3(idx, s, reason, key, &report, data);
1547 } else {
1548 self.scope_list_names.get_mut(name).unwrap().1 = otemp;
1550 Self::break_chains_to(&mut self.named, idx);
1551 self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1552 }
1553 } else if self.strict_scopes && other.is_input[oidx].is_some() {
1554 let token = other.is_input[oidx].as_ref().unwrap();
1555 let msg = format!("`{key}` expects list {name} to exist");
1556 let msg2 = "here";
1557 self.log_traceback(
1558 warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1559 )
1560 .push();
1561 } else {
1562 let (s, reason) = other.resolve_named(oidx, data);
1564 self.scope_list_names.insert(name, (self.named.len(), otemp));
1565 self.named.push(ScopeEntry::Scope(s, reason.clone()));
1566 self.is_input.push(other.is_input[oidx].clone());
1567 }
1568 }
1569
1570 for (name, &oidx) in &other.local_names {
1572 if let Some(idx) = self.local_names.get(name).copied() {
1573 let (s, reason) = other.resolve_named(oidx, data);
1574 if other.is_input[oidx].is_some() {
1575 let report = format!("local_var:{name}");
1576 self.expect_named3(idx, s, reason, key, &report, data);
1577 } else {
1578 Self::break_chains_to(&mut self.named, idx);
1580 self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1581 }
1582 } else if self.strict_scopes && other.is_input[oidx].is_some() {
1583 let token = other.is_input[oidx].as_ref().unwrap();
1584 let msg = format!("`{key}` expects local_var:{name} to be set");
1585 let msg2 = "here";
1586 self.log_traceback(
1587 warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1588 )
1589 .push();
1590 } else {
1591 let (s, reason) = other.resolve_named(oidx, data);
1593 self.local_names.insert(name, self.named.len());
1594 self.named.push(ScopeEntry::Scope(s, reason.clone()));
1595 self.is_input.push(other.is_input[oidx].clone());
1596 }
1597 }
1598
1599 for (name, &oidx) in &other.local_list_names {
1601 if let Some(idx) = self.local_list_names.get(name).copied() {
1602 let (s, reason) = other.resolve_named(oidx, data);
1603 if other.is_input[oidx].is_some() {
1604 let report = format!("local list {name}");
1605 self.expect_named3(idx, s, reason, key, &report, data);
1606 } else {
1607 Self::break_chains_to(&mut self.named, idx);
1609 self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1610 }
1611 } else if self.strict_scopes && other.is_input[oidx].is_some() {
1612 let token = other.is_input[oidx].as_ref().unwrap();
1613 let msg = format!("`{key}` expects local variable list {name} to exist");
1614 let msg2 = "here";
1615 self.log_traceback(
1616 warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1617 )
1618 .push();
1619 } else {
1620 let (s, reason) = other.resolve_named(oidx, data);
1622 self.local_list_names.insert(name, self.named.len());
1623 self.named.push(ScopeEntry::Scope(s, reason.clone()));
1624 self.is_input.push(other.is_input[oidx].clone());
1625 }
1626 }
1627 }
1628
1629 #[allow(dead_code)]
1632 pub(crate) fn destroy(mut self) {
1633 self.prev_levels = self.scope_stack.len() - 1;
1634 }
1635}
1636
1637impl Drop for ScopeContext {
1638 fn drop(&mut self) {
1640 if !panicking() {
1642 assert_eq!(
1644 self.scope_stack.len(),
1645 self.prev_levels + 1,
1646 "scope chain not properly unwound"
1647 );
1648 }
1649 }
1650}
1651
1652#[allow(unused_variables)] #[allow(unused_mut)] fn scope_type_from_name(mut name: &str) -> Option<Scopes> {
1659 #[cfg(feature = "jomini")]
1660 if let Some(real_name) = name.strip_prefix("scope:") {
1661 name = real_name;
1662 } else if let Some(real_name) = name.strip_prefix("local_var:") {
1663 name = real_name;
1664 } else {
1665 return None;
1666 }
1667
1668 #[cfg(feature = "ck3")]
1669 if Game::is_ck3() {
1670 return match name {
1671 "accolade" => Some(Scopes::Accolade),
1672 "accolade_type" => Some(Scopes::AccoladeType),
1673 "activity" => Some(Scopes::Activity),
1674 "actor"
1675 | "recipient"
1676 | "secondary_actor"
1677 | "secondary_recipient"
1678 | "mother"
1679 | "father"
1680 | "real_father"
1681 | "child"
1682 | "councillor"
1683 | "liege"
1684 | "courtier"
1685 | "guest"
1686 | "host" => Some(Scopes::Character),
1687 "army" => Some(Scopes::Army),
1688 "artifact" => Some(Scopes::Artifact),
1689 "barony" | "county" | "title" | "landed_title" => Some(Scopes::LandedTitle),
1690 "combat_side" => Some(Scopes::CombatSide),
1691 "council_task" => Some(Scopes::CouncilTask),
1692 "culture" => Some(Scopes::Culture),
1693 "faction" => Some(Scopes::Faction),
1694 "faith" => Some(Scopes::Faith),
1695 "province" => Some(Scopes::Province),
1696 "scheme" => Some(Scopes::Scheme),
1697 "struggle" => Some(Scopes::Struggle),
1698 "story" => Some(Scopes::StoryCycle),
1699 "travel_plan" => Some(Scopes::TravelPlan),
1700 "war" => Some(Scopes::War),
1701 _ => None,
1702 };
1703 }
1704
1705 #[cfg(feature = "vic3")]
1706 if Game::is_vic3() {
1707 return match name {
1710 "admiral" | "general" | "character" => Some(Scopes::Character),
1711 "actor" | "country" | "enemy_country" | "initiator" | "target_country" => {
1712 Some(Scopes::Country)
1713 }
1714 "battle" => Some(Scopes::Battle),
1715 "interest_group" => Some(Scopes::InterestGroup),
1716 "journal_entry" => Some(Scopes::JournalEntry),
1717 "market" => Some(Scopes::Market),
1718 _ => None,
1719 };
1720 }
1721
1722 #[cfg(feature = "imperator")]
1723 if Game::is_imperator() {
1724 return match name {
1725 "party" | "character_party" => Some(Scopes::Party),
1726 "employer" | "party_country" | "country" | "overlord" | "unit_owner"
1727 | "attacker_warleader" | "defender_warleader" | "former_overlord"
1728 | "target_subject" | "future_overlord" | "old_country" | "controller" | "owner"
1729 | "family_country" | "losing_side" | "home_country" => Some(Scopes::Country),
1730 "fam" | "family" => Some(Scopes::Family),
1731 "preferred_heir" | "deified_ruler" | "personal_loyalty" | "character"
1732 | "siege_controller" | "party_leader" | "next_in_family" | "ruler" | "governor"
1733 | "governor_or_ruler" | "commander" | "former_ruler" | "newborn" | "spouse"
1734 | "job_holder" | "consort" | "current_heir" | "current_ruler" | "primary_heir"
1735 | "secondary_heir" | "current_co_ruler" | "head_of_family" | "holding_owner"
1736 | "char" | "mother" | "father" => Some(Scopes::Character),
1737 "job" => Some(Scopes::Job),
1738 "legion" => Some(Scopes::Legion),
1739 "dominant_province_religion" | "religion" => Some(Scopes::Religion),
1740 "area" => Some(Scopes::Area),
1741 "region" => Some(Scopes::Region),
1742 "governorship" => Some(Scopes::Governorship),
1743 "country_culture" => Some(Scopes::CountryCulture),
1744 "location"
1745 | "unit_destination"
1746 | "unit_objective_destination"
1747 | "unit_location"
1748 | "unit_next_location"
1749 | "capital_scope"
1750 | "holy_site" => Some(Scopes::Province),
1751 "dominant_province_culture_group" | "culture_group" => Some(Scopes::CultureGroup),
1752 "dominant_province_culture" | "culture" => Some(Scopes::Culture),
1753 "owning_unit" => Some(Scopes::Unit),
1754 "deity" | "province_deity" => Some(Scopes::Deity),
1755 "state" => Some(Scopes::State),
1756 "treasure" => Some(Scopes::Treasure),
1757 "siege" => Some(Scopes::Siege),
1758 _ => None,
1759 };
1760 }
1761
1762 #[cfg(feature = "eu5")]
1763 if Game::is_eu5() {
1764 return match name {
1765 _ => None,
1767 };
1768 }
1769
1770 None
1771}