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 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 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 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 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}