tiger_lib/data/
script_values.rs

1use std::path::PathBuf;
2use std::sync::RwLock;
3
4use crate::block::{BV, Block};
5use crate::context::ScopeContext;
6use crate::everything::Everything;
7use crate::fileset::{FileEntry, FileHandler};
8use crate::helpers::{
9    BANNED_NAMES, PrefixShould, TigerHashMap, dup_error, exact_dup_error, item_prefix_should,
10};
11use crate::item::Item;
12use crate::parse::ParserMemory;
13use crate::pdxfile::PdxFile;
14use crate::report::{ErrorKey, err, warn};
15use crate::scopes::Scopes;
16use crate::script_value::{validate_non_dynamic_script_value, validate_script_value};
17use crate::token::{Loc, Token};
18use crate::variables::Variables;
19
20#[derive(Debug, Default)]
21pub struct ScriptValues {
22    scope_overrides: TigerHashMap<&'static str, Scopes>,
23    script_values: TigerHashMap<&'static str, ScriptValue>,
24}
25
26impl ScriptValues {
27    fn load_item(&mut self, key: &Token, bv: &BV) {
28        if let Some(other) = self.script_values.get(key.as_str()) {
29            if other.key.loc.kind >= key.loc.kind {
30                if other.bv.equivalent(bv) {
31                    exact_dup_error(key, &other.key, "script value");
32                } else {
33                    dup_error(key, &other.key, "script value");
34                }
35            }
36        }
37        if BANNED_NAMES.contains(&key.as_str()) {
38            let msg = "scriptedvalue has the same name as an important builtin";
39            err(ErrorKey::NameConflict).strong().msg(msg).loc(key).push();
40        } else {
41            match item_prefix_should(Item::ScriptValue, key, |key| {
42                self.script_values.get(key).map(|entry| &entry.key)
43            }) {
44                PrefixShould::Insert(name) => {
45                    let scope_override = self.scope_overrides.get(name.as_str()).copied();
46                    self.script_values
47                        .insert(name.as_str(), ScriptValue::new(name, bv.clone(), scope_override));
48                }
49                #[cfg(any(feature = "vic3", feature = "eu5"))]
50                PrefixShould::Inject(name) => match bv {
51                    BV::Value(value) => {
52                        let msg = "cannot inject a simple value";
53                        err(ErrorKey::Prefixes).msg(msg).loc(value).push();
54                    }
55                    BV::Block(block) => {
56                        self.script_values.entry(name.as_str()).and_modify(
57                            |entry| match &mut entry.bv {
58                                BV::Value(value) => {
59                                    let msg = "cannot inject into a simple value";
60                                    err(ErrorKey::Prefixes)
61                                        .msg(msg)
62                                        .loc(name)
63                                        .loc_msg(&*value, "into here")
64                                        .push();
65                                }
66                                BV::Block(old_block) => {
67                                    old_block.append(&mut block.clone());
68                                }
69                            },
70                        );
71                    }
72                },
73                PrefixShould::Ignore => (),
74            }
75        }
76    }
77
78    pub fn scan_variables(&self, registry: &mut Variables) {
79        for item in self.script_values.values() {
80            if let Some(block) = &item.bv.get_block() {
81                registry.scan(block);
82            }
83        }
84    }
85
86    pub fn exists(&self, key: &str) -> bool {
87        self.script_values.contains_key(key)
88    }
89
90    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
91        self.script_values.values().map(|item| &item.key)
92    }
93
94    pub fn validate(&self, data: &Everything) {
95        for item in self.script_values.values() {
96            item.validate(data);
97        }
98    }
99
100    pub fn validate_call(&self, key: &Token, data: &Everything, sc: &mut ScopeContext) {
101        if let Some(item) = self.script_values.get(key.as_str()) {
102            item.validate_call(key, data, sc);
103        }
104    }
105
106    pub fn validate_non_dynamic_call(&self, key: &Token, data: &Everything) {
107        if let Some(item) = self.script_values.get(key.as_str()) {
108            item.validate_non_dynamic_call(data);
109        }
110    }
111}
112
113impl FileHandler<Block> for ScriptValues {
114    fn config(&mut self, config: &Block) {
115        if let Some(block) = config.get_field_block("scope_override") {
116            for (key, token) in block.iter_assignments() {
117                let mut scopes = Scopes::empty();
118                if token.lowercase_is("all") {
119                    scopes = Scopes::all();
120                } else {
121                    for part in token.split('|') {
122                        if let Some(scope) = Scopes::from_snake_case(part.as_str()) {
123                            scopes |= scope;
124                        } else {
125                            let msg = format!("unknown scope type `{part}`");
126                            warn(ErrorKey::Config).msg(msg).loc(part).push();
127                        }
128                    }
129                }
130                self.scope_overrides.insert(key.as_str(), scopes);
131            }
132        }
133    }
134
135    fn subpath(&self) -> PathBuf {
136        PathBuf::from("common/script_values")
137    }
138
139    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
140        if !entry.filename().to_string_lossy().ends_with(".txt") {
141            return None;
142        }
143
144        PdxFile::read(entry, parser)
145    }
146
147    fn handle_file(&mut self, _entry: &FileEntry, block: Block) {
148        for (key, bv) in block.iter_assignments_and_definitions_warn() {
149            self.load_item(key, bv);
150        }
151    }
152}
153
154#[derive(Debug)]
155pub struct ScriptValue {
156    key: Token,
157    bv: BV,
158    cache: RwLock<TigerHashMap<Loc, ScopeContext>>,
159    scope_override: Option<Scopes>,
160}
161
162impl ScriptValue {
163    pub fn new(key: Token, bv: BV, scope_override: Option<Scopes>) -> Self {
164        Self { key, bv, cache: RwLock::new(TigerHashMap::default()), scope_override }
165    }
166
167    pub fn cached_compat(&self, key: &Token, sc: &mut ScopeContext, data: &Everything) -> bool {
168        if let Some(our_sc) = self.cache.read().unwrap().get(&key.loc) {
169            sc.expect_compatibility(our_sc, key, data);
170            true
171        } else {
172            false
173        }
174    }
175
176    pub fn validate(&self, data: &Everything) {
177        // For some reason, script values can be set to bools as well
178        if let Some(token) = self.bv.get_value() {
179            if token.is("yes") || token.is("no") {
180                return;
181            }
182        }
183        let mut sc = ScopeContext::new_unrooted(Scopes::all(), &self.key);
184        sc.set_strict_scopes(false);
185        if self.scope_override.is_some() {
186            sc.set_no_warn(true);
187        }
188        self.validate_call(&self.key, data, &mut sc);
189    }
190
191    pub fn validate_call(&self, key: &Token, data: &Everything, sc: &mut ScopeContext) {
192        if !self.cached_compat(key, sc, data) {
193            let mut our_sc = ScopeContext::new_unrooted(Scopes::all(), &self.key);
194            our_sc.set_strict_scopes(false);
195            if self.scope_override.is_some() {
196                our_sc.set_no_warn(true);
197            }
198            self.cache.write().unwrap().insert(key.loc, our_sc.clone());
199            validate_script_value(&self.bv, data, &mut our_sc);
200            if let Some(scopes) = self.scope_override {
201                our_sc = ScopeContext::new_unrooted(scopes, key);
202                our_sc.set_strict_scopes(false);
203            }
204            sc.expect_compatibility(&our_sc, key, data);
205            self.cache.write().unwrap().insert(key.loc, our_sc);
206        }
207    }
208
209    pub fn validate_non_dynamic_call(&self, data: &Everything) {
210        validate_non_dynamic_script_value(&self.bv, data);
211    }
212}