tiger_lib/data/
scripted_modifiers.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::context::ScopeContext;
5use crate::everything::Everything;
6use crate::fileset::{FileEntry, FileHandler};
7use crate::helpers::{BANNED_NAMES, TigerHashMap, limited_item_prefix_should_insert};
8use crate::item::Item;
9use crate::macros::{MACRO_MAP, MacroCache};
10use crate::parse::ParserMemory;
11use crate::pdxfile::PdxFile;
12use crate::report::{ErrorKey, err};
13use crate::scopes::Scopes;
14use crate::token::Token;
15use crate::tooltipped::Tooltipped;
16use crate::validate::{validate_modifiers, validate_scripted_modifier_calls};
17use crate::validator::Validator;
18use crate::variables::Variables;
19
20#[derive(Debug, Default)]
21pub struct ScriptedModifiers {
22    scripted_modifiers: TigerHashMap<&'static str, ScriptedModifier>,
23}
24
25impl ScriptedModifiers {
26    fn load_item(&mut self, key: Token, block: Block) {
27        if BANNED_NAMES.contains(&key.as_str()) {
28            let msg = "scripted modifier has the same name as an important builtin";
29            err(ErrorKey::NameConflict).strong().msg(msg).loc(key).push();
30        } else if let Some(name) =
31            limited_item_prefix_should_insert(Item::ScriptedModifier, key, |key| {
32                // TODO: here and in triggers and effects, get rid of the clone somehow.
33                self.scripted_modifiers.get(key).map(|entry| &entry.key)
34            })
35        {
36            if block.source.is_some() {
37                MACRO_MAP.insert_or_get_loc(name.loc);
38            }
39            self.scripted_modifiers.insert(name.as_str(), ScriptedModifier::new(name, block));
40        }
41    }
42
43    pub fn scan_variables(&self, registry: &mut Variables) {
44        for item in self.scripted_modifiers.values() {
45            registry.scan(&item.block);
46        }
47    }
48
49    pub fn exists(&self, key: &str) -> bool {
50        self.scripted_modifiers.contains_key(key)
51    }
52
53    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
54        self.scripted_modifiers.values().map(|item| &item.key)
55    }
56
57    pub fn get(&self, key: &str) -> Option<&ScriptedModifier> {
58        self.scripted_modifiers.get(key)
59    }
60
61    pub fn validate(&self, data: &Everything) {
62        for item in self.scripted_modifiers.values() {
63            item.validate(data);
64        }
65    }
66}
67
68impl FileHandler<Block> for ScriptedModifiers {
69    fn subpath(&self) -> PathBuf {
70        PathBuf::from("common/scripted_modifiers")
71    }
72
73    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
74        if !entry.filename().to_string_lossy().ends_with(".txt") {
75            return None;
76        }
77
78        PdxFile::read(entry, parser)
79    }
80
81    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
82        for (key, block) in block.drain_definitions_warn() {
83            self.load_item(key, block);
84        }
85    }
86}
87
88#[derive(Debug)]
89pub struct ScriptedModifier {
90    pub key: Token,
91    block: Block,
92    cache: MacroCache<ScopeContext>,
93}
94
95impl ScriptedModifier {
96    pub fn new(key: Token, block: Block) -> Self {
97        Self { key, block, cache: MacroCache::default() }
98    }
99
100    pub fn validate(&self, data: &Everything) {
101        // Validate the modifiers that aren't macros
102        if self.block.source.is_none() {
103            let mut sc = ScopeContext::new_unrooted(Scopes::all(), &self.key);
104            sc.set_strict_scopes(false);
105            self.validate_call(&self.key, data, &mut sc);
106        }
107    }
108
109    pub fn validate_call(&self, key: &Token, data: &Everything, sc: &mut ScopeContext) {
110        if !self.cached_compat(key, &[], sc, data) {
111            let mut our_sc = ScopeContext::new_unrooted(Scopes::all(), &self.key);
112            our_sc.set_strict_scopes(false);
113            self.cache.insert(key, &[], Tooltipped::No, false, our_sc.clone());
114            let mut vd = Validator::new(&self.block, data);
115            validate_modifiers(&mut vd, &mut our_sc);
116            validate_scripted_modifier_calls(vd, data, &mut our_sc);
117            sc.expect_compatibility(&our_sc, key, data);
118            self.cache.insert(key, &[], Tooltipped::No, false, our_sc);
119        }
120    }
121
122    pub fn macro_parms(&self) -> Vec<&'static str> {
123        self.block.macro_parms()
124    }
125
126    pub fn cached_compat(
127        &self,
128        key: &Token,
129        args: &[(&'static str, Token)],
130        sc: &mut ScopeContext,
131        data: &Everything,
132    ) -> bool {
133        self.cache.perform(key, args, Tooltipped::No, false, |our_sc| {
134            sc.expect_compatibility(our_sc, key, data);
135        })
136    }
137
138    pub fn validate_macro_expansion(
139        &self,
140        key: &Token,
141        args: &[(&'static str, Token)],
142        data: &Everything,
143        sc: &mut ScopeContext,
144    ) {
145        // Every invocation is treated as different even if the args are the same,
146        // because we want to point to the correct one when reporting errors.
147        if !self.cached_compat(key, args, sc, data) {
148            if let Some(block) = self.block.expand_macro(args, key.loc, &data.parser.pdxfile) {
149                let mut our_sc = ScopeContext::new_unrooted(Scopes::all(), &self.key);
150                our_sc.set_strict_scopes(false);
151                // Insert the dummy sc before continuing. That way, if we recurse, we'll hit
152                // that dummy context instead of macro-expanding again.
153                self.cache.insert(key, args, Tooltipped::No, false, our_sc.clone());
154                let mut vd = Validator::new(&block, data);
155                validate_modifiers(&mut vd, &mut our_sc);
156                validate_scripted_modifier_calls(vd, data, &mut our_sc);
157                sc.expect_compatibility(&our_sc, key, data);
158                self.cache.insert(key, args, Tooltipped::No, false, our_sc);
159            }
160        }
161    }
162}