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