1use std::fmt;
4use std::num::{ParseFloatError, ParseIntError};
5use std::str::ParseBoolError;
6
7use crate::params::{ParamDescriptor, ParamKind, ParamValue};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ValueError {
14 NotANumber {
16 raw: String,
18 source: ParseFloatError,
20 },
21 NotAnInteger {
23 raw: String,
25 source: ParseIntError,
27 },
28 NotABool {
30 raw: String,
32 source: ParseBoolError,
34 },
35 OutOfRange {
37 value: String,
39 min: String,
41 max: String,
43 },
44 UnknownOption {
46 raw: String,
48 options: &'static [&'static str],
50 },
51 UnknownParam {
53 id: String,
55 known: Vec<&'static str>,
57 },
58 BadOverride {
60 raw: String,
62 },
63 Param {
65 id: String,
67 source: Box<Self>,
69 },
70 WrongKind {
72 expected: ValueKind,
74 found: ValueKind,
76 value: String,
78 },
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum ValueKind {
84 F32,
86 U32,
88 Bool,
90 Choice,
92}
93
94impl fmt::Display for ValueKind {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.write_str(match self {
97 Self::F32 => "f32",
98 Self::U32 => "u32",
99 Self::Bool => "bool",
100 Self::Choice => "choice",
101 })
102 }
103}
104
105impl fmt::Display for ValueError {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 Self::NotANumber { raw, .. } => write!(f, "'{raw}' is not a number"),
109 Self::NotAnInteger { raw, .. } => write!(f, "'{raw}' is not an integer"),
110 Self::NotABool { raw, .. } => write!(f, "'{raw}' is not a bool"),
111 Self::OutOfRange { value, min, max } => write!(f, "{value} is outside {min}..={max}"),
112 Self::UnknownOption { raw, options } if raw.parse::<usize>().is_ok() => {
113 write!(f, "index {raw} is not one of {options:?}")
114 }
115 Self::UnknownOption { raw, options } => write!(f, "'{raw}' is not one of {options:?}"),
116 Self::UnknownParam { id, .. } => write!(f, "model has no parameter '{id}'"),
117 Self::BadOverride { raw } => write!(f, "bad --set '{raw}', expected ID=VALUE"),
118 Self::Param { id, .. } => write!(f, "parameter '{id}'"),
119 Self::WrongKind { expected, found, value } => {
120 let example = match expected {
121 ValueKind::F32 => "an f32 value such as 1.0f32",
122 ValueKind::U32 => "a u32 value such as 1u32",
123 ValueKind::Bool => "a bool value",
124 ValueKind::Choice => "a choice such as ParamValue::Choice(0)",
125 };
126 write!(f, "expected {example}, found the {found} {value}")
127 }
128 }
129 }
130}
131
132impl std::error::Error for ValueError {
133 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
134 match self {
135 Self::NotANumber { source, .. } => Some(source),
136 Self::NotAnInteger { source, .. } => Some(source),
137 Self::NotABool { source, .. } => Some(source),
138 Self::Param { source, .. } => Some(source),
139 _ => None,
140 }
141 }
142}
143
144pub fn parse_overrides(raw: &[String]) -> Result<Vec<(String, String)>, ValueError> {
150 raw.iter()
151 .map(|pair| {
152 let (id, value) = pair
153 .split_once('=')
154 .ok_or_else(|| ValueError::BadOverride { raw: pair.clone() })?;
155 Ok((id.to_owned(), value.to_owned()))
156 })
157 .collect()
158}
159
160pub fn resolve_params(
167 descriptors: &[ParamDescriptor],
168 overrides: &[(String, String)],
169) -> Result<Vec<ParamValue>, ValueError> {
170 let mut values: Vec<ParamValue> = descriptors
171 .iter()
172 .map(|descriptor| descriptor.kind.default_value())
173 .collect();
174
175 for (id, raw) in overrides {
176 let index = descriptors
177 .iter()
178 .position(|descriptor| descriptor.id == *id)
179 .ok_or_else(|| ValueError::UnknownParam {
180 id: id.clone(),
181 known: descriptors.iter().map(|descriptor| descriptor.id).collect(),
182 })?;
183 values[index] = parse_value(&descriptors[index].kind, raw).map_err(|error| ValueError::Param {
184 id: id.clone(),
185 source: Box::new(error),
186 })?;
187 }
188
189 Ok(values)
190}
191
192pub fn parse_value(kind: &ParamKind, raw: &str) -> Result<ParamValue, ValueError> {
200 let value = match kind {
201 ParamKind::F32 { .. } => ParamValue::F32(raw.parse().map_err(|source| ValueError::NotANumber {
202 raw: raw.to_owned(),
203 source,
204 })?),
205 ParamKind::U32 { .. } => ParamValue::U32(raw.parse().map_err(|source| ValueError::NotAnInteger {
206 raw: raw.to_owned(),
207 source,
208 })?),
209 ParamKind::Bool { .. } => ParamValue::Bool(raw.parse().map_err(|source| ValueError::NotABool {
210 raw: raw.to_owned(),
211 source,
212 })?),
213 ParamKind::Choice { options, .. } => {
214 let index = options
215 .iter()
216 .position(|option| *option == raw)
217 .or_else(|| raw.parse::<usize>().ok())
218 .ok_or_else(|| ValueError::UnknownOption {
219 raw: raw.to_owned(),
220 options,
221 })?;
222 ParamValue::Choice(index)
223 }
224 };
225 check_value(kind, &value)?;
226 Ok(value)
227}
228
229pub fn check_value(kind: &ParamKind, value: &ParamValue) -> Result<(), ValueError> {
237 let out_of_range = |value: &dyn fmt::Display, min: &dyn fmt::Display, max: &dyn fmt::Display| {
238 Err(ValueError::OutOfRange {
239 value: value.to_string(),
240 min: min.to_string(),
241 max: max.to_string(),
242 })
243 };
244 match (kind, value) {
245 (ParamKind::F32 { min, max, .. }, ParamValue::F32(number)) => {
246 if number.is_finite() && number >= min && number <= max {
247 Ok(())
248 } else {
249 out_of_range(number, min, max)
250 }
251 }
252 (ParamKind::U32 { min, max, .. }, ParamValue::U32(number)) => {
253 if number >= min && number <= max {
254 Ok(())
255 } else {
256 out_of_range(number, min, max)
257 }
258 }
259 (ParamKind::Bool { .. }, ParamValue::Bool(_)) => Ok(()),
260 (ParamKind::Choice { options, .. }, ParamValue::Choice(index)) if *index < options.len() => Ok(()),
261 (ParamKind::Choice { options, .. }, ParamValue::Choice(index)) => Err(ValueError::UnknownOption {
262 raw: index.to_string(),
263 options,
264 }),
265 (kind, other) => Err(ValueError::WrongKind {
266 expected: param_kind(kind),
267 found: value_kind(other),
268 value: plain_text(other),
269 }),
270 }
271}
272
273fn param_kind(kind: &ParamKind) -> ValueKind {
275 match kind {
276 ParamKind::F32 { .. } => ValueKind::F32,
277 ParamKind::U32 { .. } => ValueKind::U32,
278 ParamKind::Bool { .. } => ValueKind::Bool,
279 ParamKind::Choice { .. } => ValueKind::Choice,
280 }
281}
282
283fn value_kind(value: &ParamValue) -> ValueKind {
285 match value {
286 ParamValue::F32(_) => ValueKind::F32,
287 ParamValue::U32(_) => ValueKind::U32,
288 ParamValue::Bool(_) => ValueKind::Bool,
289 ParamValue::Choice(_) => ValueKind::Choice,
290 }
291}
292
293pub fn format_value(kind: &ParamKind, value: &ParamValue) -> String {
297 match (kind, value) {
298 (ParamKind::Choice { options, .. }, ParamValue::Choice(index)) => options
299 .get(*index)
300 .map_or_else(|| index.to_string(), |&name| name.to_owned()),
301 _ => plain_text(value),
302 }
303}
304
305fn plain_text(value: &ParamValue) -> String {
307 match value {
308 ParamValue::F32(number) => number.to_string(),
309 ParamValue::U32(number) => number.to_string(),
310 ParamValue::Bool(flag) => flag.to_string(),
311 ParamValue::Choice(index) => index.to_string(),
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::{ValueError, ValueKind, check_value, format_value, parse_overrides, parse_value, resolve_params};
318 use crate::helpers::{bool_param, f32_param, u32_param};
319 use crate::params::{ParamApply, ParamDescriptor, ParamFormat, ParamKind, ParamValue};
320
321 const OPTIONS: &[&str] = &["moore", "von_neumann"];
322
323 fn descriptors() -> Vec<ParamDescriptor> {
324 vec![
325 u32_param("num_agents", "Agents", 1000, 1, 5_000_000),
326 f32_param("cohesion", "Cohesion", 0.5, 0.0, 1.0, None),
327 ParamDescriptor {
328 id: "neighborhood",
329 label: "Neighborhood",
330 kind: ParamKind::Choice {
331 options: OPTIONS,
332 default: 0,
333 },
334 apply: ParamApply::Live,
335 format: ParamFormat::Plain,
336 },
337 ]
338 }
339
340 fn resolve(raw: &str) -> Result<Vec<ParamValue>, ValueError> {
341 let overrides = parse_overrides(&[raw.to_owned()])?;
342 resolve_params(&descriptors(), &overrides)
343 }
344
345 fn message(error: &ValueError) -> String {
347 let mut text = error.to_string();
348 let mut source = std::error::Error::source(error);
349 while let Some(inner) = source {
350 text = format!("{text}: {inner}");
351 source = inner.source();
352 }
353 text
354 }
355
356 #[test]
357 fn an_override_replaces_one_default_and_leaves_the_rest() {
358 let values = resolve("num_agents=2500").expect("2500 agents is in range");
359 assert_eq!(values[0], ParamValue::U32(2500));
360 assert_eq!(values[1], ParamValue::F32(0.5));
361 assert_eq!(values[2], ParamValue::Choice(0));
362 }
363
364 #[test]
367 fn a_value_outside_the_descriptor_range_is_refused() {
368 let error = resolve("num_agents=4000000000").expect_err("4e9 agents is over the maximum");
369 assert!(message(&error).contains("5000000"), "{}", message(&error));
370
371 let error = resolve("num_agents=0").expect_err("0 agents is under the minimum");
372 assert!(message(&error).contains("num_agents"), "{}", message(&error));
373
374 assert!(resolve("cohesion=1.5").is_err(), "1.5 is over the maximum");
375 assert!(resolve("cohesion=-0.5").is_err(), "-0.5 is under the minimum");
376 assert!(resolve("cohesion=nan").is_err(), "NaN is in no range");
377 }
378
379 #[test]
381 fn the_limits_themselves_are_accepted() {
382 assert_eq!(resolve("cohesion=0").expect("the minimum")[1], ParamValue::F32(0.0));
383 assert_eq!(resolve("cohesion=1").expect("the maximum")[1], ParamValue::F32(1.0));
384 assert_eq!(
385 resolve("num_agents=5000000").expect("the maximum")[0],
386 ParamValue::U32(5_000_000)
387 );
388 }
389
390 #[test]
392 fn a_choice_index_is_checked_against_the_options() {
393 assert_eq!(
394 resolve("neighborhood=1").expect("index 1 exists")[2],
395 ParamValue::Choice(1)
396 );
397 assert_eq!(
398 resolve("neighborhood=von_neumann").expect("a label")[2],
399 ParamValue::Choice(1)
400 );
401 assert!(resolve("neighborhood=2").is_err(), "there is no third option");
402 assert!(resolve("neighborhood=hexagonal").is_err(), "no such label");
403 }
404
405 #[test]
406 fn an_unknown_parameter_is_refused() {
407 assert!(resolve("no_such_param=1").is_err());
408 assert!(
409 parse_overrides(&["num_agents".to_owned()]).is_err(),
410 "no '=' in the pair"
411 );
412 }
413
414 #[test]
415 fn text_of_the_wrong_kind_keeps_the_parser_error() {
416 let error = resolve("num_agents=abc").expect_err("not an integer");
417 assert_eq!(
418 message(&error),
419 "parameter 'num_agents': 'abc' is not an integer: invalid digit found in string",
420 "the parser's error is the last source"
421 );
422 let error = resolve("cohesion=high").expect_err("not a number");
423 assert!(
424 message(&error).ends_with("invalid float literal"),
425 "{}",
426 message(&error)
427 );
428 }
429
430 #[test]
431 fn a_formatted_value_parses_back_to_itself() {
432 let mut descriptors = descriptors();
433 descriptors.push(bool_param("wrap", "Wrap", true));
434 let cases = [
435 (0, ParamValue::U32(1)),
436 (0, ParamValue::U32(5_000_000)),
437 (1, ParamValue::F32(0.0)),
438 (1, ParamValue::F32(0.1)),
439 (1, ParamValue::F32(1.0 / 3.0)),
440 (1, ParamValue::F32(f32::from_bits(1))),
441 (1, ParamValue::F32(1.0)),
442 (2, ParamValue::Choice(0)),
443 (2, ParamValue::Choice(1)),
444 (3, ParamValue::Bool(false)),
445 (3, ParamValue::Bool(true)),
446 ];
447 for (index, value) in cases {
448 let kind = &descriptors[index].kind;
449 let text = format_value(kind, &value);
450 assert_eq!(parse_value(kind, &text), Ok(value), "{text}");
451 }
452 assert_eq!(
453 format_value(&descriptors[2].kind, &ParamValue::Choice(1)),
454 "von_neumann"
455 );
456 }
457
458 #[test]
461 fn a_choice_reads_an_option_name_before_an_index() {
462 let kind = ParamKind::Choice {
463 options: &["low", "0"],
464 default: 0,
465 };
466 assert_eq!(parse_value(&kind, "0"), Ok(ParamValue::Choice(1)), "the name wins");
467 assert_eq!(
468 parse_value(&kind, "1"),
469 Ok(ParamValue::Choice(1)),
470 "an index still reads"
471 );
472 let kind = ParamKind::Choice {
473 options: &["low", "2"],
474 default: 0,
475 };
476 for index in 0..2 {
477 let value = ParamValue::Choice(index);
478 assert_eq!(parse_value(&kind, &format_value(&kind, &value)), Ok(value));
479 }
480 }
481
482 #[test]
483 fn a_value_that_does_not_fit_its_kind_is_refused() {
484 let descriptors = descriptors();
485 assert!(check_value(&descriptors[0].kind, &ParamValue::F32(10.0)).is_err());
486 assert!(check_value(&descriptors[1].kind, &ParamValue::U32(0)).is_err());
487 assert!(check_value(&descriptors[1].kind, &ParamValue::F32(f32::INFINITY)).is_err());
488 assert!(check_value(&descriptors[2].kind, &ParamValue::Choice(2)).is_err());
489 assert_eq!(check_value(&descriptors[2].kind, &ParamValue::Choice(1)), Ok(()));
490 }
491
492 #[test]
494 fn a_value_of_another_kind_names_both_kinds() {
495 let descriptors = descriptors();
496 let error = check_value(&descriptors[1].kind, &ParamValue::U32(1)).expect_err("a u32 for an f32");
497 assert_eq!(
498 error,
499 ValueError::WrongKind {
500 expected: ValueKind::F32,
501 found: ValueKind::U32,
502 value: "1".to_owned(),
503 }
504 );
505 assert_eq!(
506 error.to_string(),
507 "expected an f32 value such as 1.0f32, found the u32 1"
508 );
509 let error = check_value(&descriptors[0].kind, &ParamValue::F32(256.0)).expect_err("an f32 for a u32");
510 assert_eq!(
511 error.to_string(),
512 "expected a u32 value such as 1u32, found the f32 256"
513 );
514 let error = check_value(&descriptors[2].kind, &ParamValue::U32(1)).expect_err("a u32 for a choice");
515 assert!(
516 matches!(
517 error,
518 ValueError::WrongKind {
519 expected: ValueKind::Choice,
520 found: ValueKind::U32,
521 ..
522 }
523 ),
524 "{error:?}"
525 );
526 assert_eq!(
527 error.to_string(),
528 "expected a choice such as ParamValue::Choice(0), found the u32 1"
529 );
530 let wrap = bool_param("wrap", "Wrap", true);
531 let error = check_value(&wrap.kind, &ParamValue::Choice(0)).expect_err("a choice for a bool");
532 assert_eq!(error.to_string(), "expected a bool value, found the choice 0");
533 }
534}