1use crate::block::{BV, Block, BlockItem, Comparator, Eq::*};
8use crate::context::{Reason, ScopeContext, Temporary};
9use crate::everything::Everything;
10use crate::helpers::TriBool;
11use crate::item::Item;
12use crate::lowercase::Lowercase;
13use crate::report::{ErrorKey, Severity, err, tips, untidy, warn};
14use crate::scopes::{Scopes, scope_iterator};
15use crate::token::Token;
16use crate::tooltipped::Tooltipped;
17use crate::trigger::{validate_target_ok_this, validate_trigger, validate_trigger_key_bv};
18use crate::validate::{
19 ListType, precheck_iterator_fields, validate_ifelse_sequence, validate_inside_iterator,
20 validate_iterator_fields, validate_scope_chain,
21};
22use crate::validator::Validator;
23
24fn validate_inner(
34 mut vd: Validator,
35 block: &Block,
36 data: &Everything,
37 sc: &mut ScopeContext,
38 mut have_value: TriBool,
39 check_desc: bool,
40) -> bool {
41 if check_desc {
42 vd.field_item("desc", Item::Localization);
43 vd.field_item("format", Item::Localization);
44 } else {
45 vd.field_value("desc");
46 vd.field_value("format");
47 }
48
49 let mut made_changes = false;
51 let mut saved_value = false;
53
54 validate_ifelse_sequence(block, "if", "else_if", "else");
55 vd.set_allow_questionmark_equals(true);
56 vd.unknown_fields_cmp(|token, cmp, bv| {
57 if token.is("save_temporary_scope_as") {
58 if let Some(name) = bv.expect_value() {
60 sc.save_current_scope(name.as_str(), Temporary::Yes);
61 made_changes = true;
62 }
63 } else if token.is("save_temporary_value_as") {
64 if let Some(name) = bv.expect_value() {
65 sc.define_name_token(name.as_str(), Scopes::Value, name, Temporary::Yes);
66 made_changes = true;
67 saved_value = true;
68 }
69 } else if token.is("value") {
70 if have_value == TriBool::True && !saved_value {
71 let msg = "setting value here will overwrite the previous calculations";
72 warn(ErrorKey::Logic).msg(msg).loc(token).push();
73 }
74 have_value = TriBool::True;
75 saved_value = false;
76 validate_bv(bv, data, sc, check_desc);
77 made_changes = true;
78 } else if token.is("add") || token.is("subtract") || token.is("min") || token.is("max") {
79 have_value = TriBool::True;
80 saved_value = false;
81 validate_bv(bv, data, sc, check_desc);
82 made_changes = true;
83 } else if token.is("multiply")
84 || token.is("divide")
85 || token.is("modulo")
86 || token.is("round_to")
87 {
88 if have_value == TriBool::False {
89 let msg = format!("nothing to {token} yet");
90 warn(ErrorKey::Logic).msg(msg).loc(token).push();
91 }
92 validate_bv(bv, data, sc, check_desc);
93 made_changes = true;
94 saved_value = false;
95 } else if token.is("round") || token.is("ceiling") || token.is("floor") || token.is("abs") {
96 if have_value == TriBool::False {
97 let msg = format!("nothing to {token} yet");
98 warn(ErrorKey::Logic).msg(msg).loc(token).push();
99 }
100 if let Some(value) = bv.expect_value() {
101 #[cfg(not(feature = "imperator"))]
102 if !value.is("yes") && !value.is("no") {
103 let msg = "expected yes or no";
104 warn(ErrorKey::Validation).msg(msg).loc(value).push();
105 }
106 #[cfg(feature = "imperator")]
107 if !token.is("round") && !value.is("yes") && !value.is("no") {
108 let msg = "expected yes or no";
109 warn(ErrorKey::Validation).msg(msg).loc(value).push();
110 }
111 #[cfg(feature = "imperator")]
112 if token.is("round")
113 && !&["yes", "no", "floor", "ceiling"].iter().any(|&v| value.is(v))
114 {
115 let msg = "expected yes, no, floor, or ceiling";
117 warn(ErrorKey::Validation).msg(msg).loc(value).push();
118 }
119 made_changes = true;
120 saved_value = false;
121 }
122 } else if token.is("fixed_range") || token.is("integer_range") {
123 if have_value == TriBool::True {
124 let msg = "using fixed_range here will overwrite the previous calculations";
125 warn(ErrorKey::Logic).msg(msg).loc(token).push();
126 }
127 if let Some(block) = bv.expect_block() {
128 validate_minmax_range(block, data, sc, check_desc);
129 made_changes = true;
130 saved_value = false;
131 }
132 have_value = TriBool::True;
133 } else if token.is("if") || token.is("else_if") {
134 if let Some(block) = bv.expect_block() {
135 validate_if(token, block, data, sc, check_desc);
136 made_changes = true;
137 }
138 have_value = TriBool::Maybe;
139 } else if token.is("else") {
140 if let Some(block) = bv.expect_block() {
141 validate_else(token, block, data, sc, check_desc);
142 made_changes = true;
143 }
144 have_value = TriBool::Maybe;
145 } else if token.is("switch") {
146 if let Some(block) = bv.expect_block() {
147 let mut vd = Validator::new(block, data);
148 vd.req_field("trigger");
149 if let Some(target) = vd.field_value("trigger").cloned() {
150 vd.set_allow_questionmark_equals(true);
151 vd.unknown_block_fields(|key, block| {
152 if !key.is("fallback") {
153 let synthetic_bv = BV::Value(key.clone());
154 validate_trigger_key_bv(
155 &target,
156 Comparator::Equals(Single),
157 &synthetic_bv,
158 data,
159 sc,
160 Tooltipped::No,
161 false,
162 Severity::Error,
163 );
164 }
165 let vd = Validator::new(block, data);
166 made_changes |= validate_inner(vd, block, data, sc, have_value, check_desc);
167 });
168 have_value = TriBool::Maybe;
169 }
170 }
171 } else {
172 if let Some((it_type, it_name)) = token.split_once('_') {
173 if let Ok(ltype) = ListType::try_from(it_type.as_str()) {
174 if let Some((inscopes, outscope)) = scope_iterator(&it_name, data, sc) {
175 if ltype.is_for_triggers() {
176 let msg = "cannot use `any_` iterators in a script value";
177 err(ErrorKey::Validation).msg(msg).loc(token).push();
178 }
179 sc.expect(inscopes, &Reason::Token(token.clone()), data);
180 if let Some(block) = bv.expect_block() {
181 precheck_iterator_fields(ltype, it_name.as_str(), block, data, sc);
182 sc.open_scope(outscope, token.clone());
183 validate_iterator(ltype, &it_name, block, data, sc, check_desc);
184 made_changes = true;
185 sc.close();
186 have_value = TriBool::Maybe;
187 }
188 }
189 return;
190 }
191 }
192
193 sc.open_builder();
195 if validate_scope_chain(token, data, sc, matches!(cmp, Comparator::Equals(Question))) {
196 if let Some(block) = bv.expect_block() {
197 sc.finalize_builder();
198 let vd = Validator::new(block, data);
199 made_changes |= validate_inner(vd, block, data, sc, have_value, check_desc);
200 have_value = TriBool::Maybe;
201 }
202 }
203 sc.close();
204 }
205 });
206 made_changes
207}
208
209fn validate_iterator(
212 ltype: ListType,
213 it_name: &Token,
214 block: &Block,
215 data: &Everything,
216 sc: &mut ScopeContext,
217 check_desc: bool,
218) {
219 let mut side_effects = false;
220 let mut vd = Validator::new(block, data);
221 vd.field_validated_block("limit", |block, data| {
222 side_effects |= validate_trigger(block, data, sc, Tooltipped::No);
223 });
224
225 let mut tooltipped = Tooltipped::No;
226 validate_iterator_fields(Lowercase::empty(), ltype, data, sc, &mut vd, &mut tooltipped, true);
227
228 validate_inside_iterator(
229 &Lowercase::new(it_name.as_str()),
230 ltype,
231 block,
232 data,
233 sc,
234 &mut vd,
235 Tooltipped::No,
236 );
237
238 side_effects |= validate_inner(vd, block, data, sc, TriBool::Maybe, check_desc);
239 if !side_effects {
240 let msg = "this iterator does not change the script value";
241 let info = "it should be either removed, or changed to do something useful";
242 err(ErrorKey::Logic).msg(msg).info(info).loc(block).push();
243 }
244}
245
246fn validate_minmax_range(
249 block: &Block,
250 data: &Everything,
251 sc: &mut ScopeContext,
252 check_desc: bool,
253) {
254 let mut vd = Validator::new(block, data);
255 vd.req_field("min");
256 vd.req_field("max");
257 vd.multi_field_validated("min", |bv, data| {
258 validate_bv(bv, data, sc, check_desc);
259 });
260 vd.multi_field_validated("max", |bv, data| {
261 validate_bv(bv, data, sc, check_desc);
262 });
263}
264
265fn validate_if(
268 key: &Token,
269 block: &Block,
270 data: &Everything,
271 sc: &mut ScopeContext,
272 check_desc: bool,
273) {
274 let mut side_effects = false;
275 let mut vd = Validator::new(block, data);
276 vd.req_field_warn("limit");
277 vd.field_validated_block("limit", |block, data| {
278 side_effects |= validate_trigger(block, data, sc, Tooltipped::No);
279 });
280 side_effects |= validate_inner(vd, block, data, sc, TriBool::Maybe, check_desc);
281
282 if !side_effects {
283 let msg = format!("this `{key}` does not change the script value");
284 err(ErrorKey::Logic).weak().msg(msg).loc(key).push();
287 }
288}
289
290fn validate_else(
293 key: &Token,
294 block: &Block,
295 data: &Everything,
296 sc: &mut ScopeContext,
297 check_desc: bool,
298) {
299 let mut side_effects = false;
300 let mut vd = Validator::new(block, data);
301 vd.field_validated_key_block("limit", |key, block, data| {
302 let msg = "`else` with a `limit` does work, but may indicate a mistake";
303 let info = "normally you would use `else_if` instead.";
304 tips(ErrorKey::IfElse).msg(msg).info(info).loc(key).push();
305 side_effects |= validate_trigger(block, data, sc, Tooltipped::No);
306 });
307 side_effects |= validate_inner(vd, block, data, sc, TriBool::Maybe, check_desc);
308
309 if !side_effects {
310 let msg = format!("this `{key}` does not change the script value");
311 let info = "it should be either removed, or changed to do something useful";
312 untidy(ErrorKey::Logic).msg(msg).info(info).loc(key).push();
314 }
315}
316
317pub(crate) fn validate_bv(bv: &BV, data: &Everything, sc: &mut ScopeContext, check_desc: bool) {
322 match bv {
324 BV::Value(t) => {
325 validate_target_ok_this(t, data, sc, Scopes::Value | Scopes::Bool);
326 }
327 BV::Block(b) => {
328 let mut vd = Validator::new(b, data);
329 if matches!(b.iter_items().next(), Some(BlockItem::Block(_) | BlockItem::Value(_))) {
330 let vec = vd.values();
332 if vec.len() == 2 {
333 for v in vec {
334 validate_target_ok_this(v, data, sc, Scopes::Value | Scopes::Bool);
335 }
336 } else {
337 warn(ErrorKey::Validation).msg("invalid script value range").loc(b).push();
338 }
339 } else {
340 validate_inner(vd, b, data, sc, TriBool::False, check_desc);
341 }
342 }
343 }
344}
345
346pub fn validate_script_value(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
347 validate_bv(bv, data, sc, true);
348}
349
350#[allow(dead_code)]
351pub fn validate_script_value_no_breakdown(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
352 validate_bv(bv, data, sc, false);
353}
354
355pub fn validate_non_dynamic_script_value(bv: &BV, data: &Everything) {
358 if let Some(token) = bv.get_value() {
359 if token.is_number() || token.is("yes") || token.is("no") {
360 return;
361 }
362 if data.script_values.exists(token.as_str()) {
363 data.script_values.validate_non_dynamic_call(token, data);
364 return;
365 }
366 }
367 let msg = "dynamic script values are not allowed here";
368 let info = "only literal numbers or the name of a simple script value";
369 err(ErrorKey::Validation).msg(msg).info(info).loc(bv).push();
370}