1use camino::Utf8Path;
2use ecolor::Color32;
3use num::BigUint;
4use std::collections::HashMap;
5use std::fs;
6use std::sync::OnceLock;
7use surfer_translation_types::{
8 BasicTranslator, TranslationPreference, ValueKind, VariableValue, extend_string,
9 kind_for_binary_representation,
10};
11use thiserror::Error;
12
13use crate::{
14 translation::check_single_wordlength,
15 wave_container::{ScopeId, VarId, VariableMeta},
16};
17
18static KIND_COLOR_KEYWORDS: OnceLock<HashMap<&'static str, ValueKind>> = OnceLock::new();
19
20fn kind_color_keywords() -> &'static HashMap<&'static str, ValueKind> {
21 KIND_COLOR_KEYWORDS.get_or_init(|| {
22 let mut m = HashMap::new();
23 m.insert("normal", ValueKind::Normal);
24 m.insert("default", ValueKind::Normal);
25 m.insert("undef", ValueKind::Undef);
26 m.insert("highimp", ValueKind::HighImp);
27 m.insert("warn", ValueKind::Warn);
28 m.insert("dontcare", ValueKind::DontCare);
29 m.insert("weak", ValueKind::Weak);
30 m.insert("error", ValueKind::Error);
31 m.insert("black", ValueKind::Custom(Color32::BLACK));
32 m.insert("white", ValueKind::Custom(Color32::WHITE));
33 m.insert("red", ValueKind::Custom(Color32::RED));
34 m.insert("green", ValueKind::Custom(Color32::GREEN));
35 m.insert("blue", ValueKind::Custom(Color32::BLUE));
36 m.insert("yellow", ValueKind::Custom(Color32::YELLOW));
37 m.insert("cyan", ValueKind::Custom(Color32::CYAN));
38 m.insert("magenta", ValueKind::Custom(Color32::MAGENTA));
39 m.insert("gray", ValueKind::Custom(Color32::GRAY));
40 m.insert("grey", ValueKind::Custom(Color32::GRAY));
41 m.insert("light_gray", ValueKind::Custom(Color32::LIGHT_GRAY));
42 m.insert("light_grey", ValueKind::Custom(Color32::LIGHT_GRAY));
43 m.insert("dark_gray", ValueKind::Custom(Color32::DARK_GRAY));
44 m.insert("dark_grey", ValueKind::Custom(Color32::DARK_GRAY));
45 m.insert("brown", ValueKind::Custom(Color32::BROWN));
46 m.insert("dark_red", ValueKind::Custom(Color32::DARK_RED));
47 m.insert("light_red", ValueKind::Custom(Color32::LIGHT_RED));
48 m.insert("orange", ValueKind::Custom(Color32::ORANGE));
49 m.insert("light_yellow", ValueKind::Custom(Color32::LIGHT_YELLOW));
50 m.insert("khaki", ValueKind::Custom(Color32::KHAKI));
51 m.insert("dark_green", ValueKind::Custom(Color32::DARK_GREEN));
52 m.insert("light_green", ValueKind::Custom(Color32::LIGHT_GREEN));
53 m.insert("dark_blue", ValueKind::Custom(Color32::DARK_BLUE));
54 m.insert("light_blue", ValueKind::Custom(Color32::LIGHT_BLUE));
55 m.insert("purple", ValueKind::Custom(Color32::PURPLE));
56 m.insert("gold", ValueKind::Custom(Color32::GOLD));
57 m
58 })
59}
60
61#[derive(Debug, Clone, PartialEq)]
62struct MappingEntry {
63 label: String,
64 kind: ValueKind,
65}
66
67#[derive(Debug, Clone)]
68struct MappingTranslatorMap {
70 name: String,
71 bits: u32,
72 entries: HashMap<VariableValue, MappingEntry>,
73}
74
75pub struct MappingTranslator {
76 map: MappingTranslatorMap,
77}
78
79#[derive(Debug, Error)]
80pub enum MappingParseError {
81 #[error("IO error: {0}")]
82 Io(#[from] std::io::Error),
83
84 #[error("Invalid bits value: {0}")]
85 InvalidBitsValue(String),
86
87 #[error("Invalid hex number: {0}")]
88 InvalidHex(String),
89
90 #[error("Invalid octal number: {0}")]
91 InvalidOctal(String),
92
93 #[error("Unknown kind/color: {0}")]
94 UnknownKindColor(String),
95
96 #[error(
97 "Binary string '{value}' requires {required} bits, but only {specified} bits specified"
98 )]
99 BinaryTooWide {
100 value: String,
101 required: u32,
102 specified: u32,
103 },
104
105 #[error(
106 "String '{value}' has {value_len} characters, expected {expected} characters to match bit width"
107 )]
108 StringLengthMismatch {
109 value: String,
110 value_len: u32,
111 expected: u32,
112 },
113
114 #[error(
115 "String '{value}' contains invalid characters. Only 01zx-hlwu are allowed. Maybe you are missing a 0b prefix for a binary string?"
116 )]
117 InvalidStringCharacters { value: String },
118
119 #[error("Missing mapping")]
120 MissingMapping,
121
122 #[error("Empty line")]
123 EmptyLine,
124
125 #[error("Line {line}: {message}\n Content: {content}")]
126 LineError {
127 line: usize,
128 content: String,
129 message: String,
130 },
131}
132
133impl MappingTranslator {
134 pub fn new_from_file<P: AsRef<Utf8Path>>(path: P) -> Result<Self, MappingParseError> {
135 Ok(MappingTranslator {
136 map: MappingTranslatorMap::new(path)?,
137 })
138 }
139
140 pub fn bits(&self) -> u32 {
141 self.map.bits
142 }
143}
144
145impl BasicTranslator<VarId, ScopeId> for MappingTranslator {
146 fn name(&self) -> String {
147 self.map.name.clone()
148 }
149
150 fn basic_translate(&self, num_bits: u32, value: &VariableValue) -> (String, ValueKind) {
151 let lookup_value = match value {
153 VariableValue::BigUint(_) => value.clone(),
154 VariableValue::String(s) => {
155 let extended = format!("{extra_bits}{s}", extra_bits = extend_string(s, num_bits));
156 VariableValue::String(extended)
157 }
158 };
159
160 if let Some(entry) = self.map.entries.get(&lookup_value) {
161 return (entry.label.clone(), entry.kind);
162 }
163
164 let var_string = key_display(&lookup_value, num_bits);
165
166 let val_kind = kind_for_binary_representation(&var_string);
167 (var_string, val_kind)
168 }
169
170 fn translates(&self, variable: &VariableMeta) -> eyre::Result<TranslationPreference> {
171 check_single_wordlength(variable.num_bits, self.map.bits)
172 }
173}
174
175impl MappingTranslatorMap {
176 pub fn new<P: AsRef<Utf8Path>>(file: P) -> Result<Self, MappingParseError> {
177 parse_file(file)
178 }
179}
180
181fn parse_file<P: AsRef<Utf8Path>>(path: P) -> Result<MappingTranslatorMap, MappingParseError> {
183 let utf = path.as_ref();
184 let content = fs::read_to_string(utf.as_std_path())?;
185
186 let default_name = utf.file_stem().map(|s| s.to_string());
188
189 parse_content_with_default_name(&content, default_name)
190}
191
192fn parse_content_with_default_name(
194 content: &str,
195 default_name: Option<String>,
196) -> Result<MappingTranslatorMap, MappingParseError> {
197 let mut name = None;
198 let mut bits = None;
199 let mut raw_entries = Vec::new();
200
201 for (line_num, line_str) in content.lines().enumerate() {
203 let processed = line_str.trim();
204
205 if processed.is_empty() || processed.starts_with('#') {
207 continue;
208 }
209
210 if let Some(name_value) = parse_specifier(processed, "name") {
212 if name.is_some() {
213 return Err(MappingParseError::LineError {
214 line: line_num + 1,
215 content: line_str.to_string(),
216 message: "Multiple Name specifiers found".to_string(),
217 });
218 }
219 name = Some(name_value);
220 continue;
221 }
222
223 if let Some(bits_value) = parse_specifier(processed, "bits") {
225 if bits.is_some() {
226 return Err(MappingParseError::LineError {
227 line: line_num + 1,
228 content: line_str.to_string(),
229 message: "Multiple Bits specifiers found".to_string(),
230 });
231 }
232 let bits_num = bits_value
233 .parse::<u32>()
234 .map_err(|_| MappingParseError::InvalidBitsValue(bits_value.clone()))?;
235 bits = Some(bits_num);
236 continue;
237 }
238
239 match parse_line(processed) {
240 Ok(entry) => raw_entries.push(entry),
241 Err(e) => {
242 return Err(MappingParseError::LineError {
243 line: line_num + 1,
244 content: line_str.to_string(),
245 message: e.to_string(),
246 });
247 }
248 }
249 }
250
251 let bit_width = bits.unwrap_or_else(|| {
253 raw_entries.iter().map(|entry| entry.3).max().unwrap_or(0)
255 });
256
257 let mut entries = HashMap::new();
259 for (value, label, kind, value_len) in raw_entries {
260 let key = normalize_first_column(&value, value_len, bit_width)?;
261
262 if entries.contains_key(&key) {
263 tracing::warn!(
264 "Duplicate mapping key '{}' encountered; keeping first occurrence",
265 key_display(&key, bit_width)
266 );
267 continue;
268 }
269 entries.insert(key, MappingEntry { label, kind });
270 }
271
272 let final_name = name
274 .or(default_name)
275 .unwrap_or_else(|| "Unknown mapping".to_string());
276
277 Ok(MappingTranslatorMap {
278 name: final_name,
279 bits: bit_width,
280 entries,
281 })
282}
283
284fn normalize_first_column(
290 value: &VariableValue,
291 value_len: u32,
292 bit_width: u32,
293) -> Result<VariableValue, MappingParseError> {
294 match value {
295 VariableValue::BigUint(v) => {
296 if value_len > bit_width {
297 return Err(MappingParseError::BinaryTooWide {
298 value: format!("{v:0width$b}", width = value_len as usize),
299 required: value_len,
300 specified: bit_width,
301 });
302 }
303 Ok(VariableValue::BigUint(v.clone()))
304 }
305 VariableValue::String(s) => {
306 if s.chars().all(|c| c == '0' || c == '1') {
308 if value_len > bit_width {
309 return Err(MappingParseError::BinaryTooWide {
310 value: s.clone(),
311 required: value_len,
312 specified: bit_width,
313 });
314 }
315
316 let value = BigUint::parse_bytes(s.as_bytes(), 2)
317 .expect("binary string should parse as BigUint");
318 Ok(VariableValue::BigUint(value))
319 } else {
320 if s.len() > bit_width as usize {
322 return Err(MappingParseError::StringLengthMismatch {
323 value: s.clone(),
324 value_len: s.len() as u32,
325 expected: bit_width,
326 });
327 }
328
329 let extended = format!(
330 "{extra}{body}",
331 extra = extend_string(s, bit_width),
332 body = s
333 );
334 Ok(VariableValue::String(extended))
335 }
336 }
337 }
338}
339
340#[inline]
341fn key_display(key: &VariableValue, bit_width: u32) -> String {
342 match key {
343 VariableValue::String(s) => s.clone(),
344 VariableValue::BigUint(v) => format!("{v:0width$b}", width = bit_width as usize),
345 }
346}
347
348fn parse_line(line: &str) -> Result<(VariableValue, String, ValueKind, u32), MappingParseError> {
350 let mut chars = line.char_indices().peekable();
351 while let Some((_, ch)) = chars.peek() {
352 if ch.is_whitespace() {
353 chars.next();
354 } else {
355 break;
356 }
357 }
358
359 let start = if let Some((idx, _)) = chars.peek().copied() {
360 idx
361 } else {
362 return Err(MappingParseError::EmptyLine);
363 };
364
365 let mut first_end = line.len();
366 for (idx, ch) in chars {
367 if ch.is_whitespace() {
368 first_end = idx;
369 break;
370 }
371 }
372
373 let first_token = &line[start..first_end];
374 let remainder = line[first_end..].trim_start();
375
376 if first_token.is_empty() {
377 return Err(MappingParseError::EmptyLine);
378 }
379
380 let (first, kind, first_len) = parse_key_with_kind(first_token)?;
381
382 if remainder.is_empty() {
383 return Err(MappingParseError::MissingMapping);
384 }
385
386 Ok((first, remainder.to_string(), kind, first_len))
387}
388
389fn parse_key_value(token: &str) -> Result<(VariableValue, u32), MappingParseError> {
394 let cleaned = token.replace('_', "");
396
397 if cleaned.starts_with("0b") || cleaned.starts_with("0B") {
399 let bin_str = &cleaned[2..];
400 if let Some(val) = BigUint::parse_bytes(bin_str.as_bytes(), 2) {
401 let bits = bin_str.len() as u32;
403 return Ok((VariableValue::BigUint(val), bits));
404 }
405 let lower = bin_str.to_lowercase();
407
408 if lower
410 .chars()
411 .all(|c| matches!(c, '0' | '1' | 'z' | 'x' | '-' | 'h' | 'l' | 'w' | 'u'))
412 {
413 return Ok((VariableValue::String(lower.clone()), lower.len() as u32));
414 }
415
416 return Err(MappingParseError::InvalidBitsValue(token.to_string()));
417 }
418
419 if cleaned.starts_with("0o") || cleaned.starts_with("0O") {
421 let oct_str = &cleaned[2..];
422 let num = BigUint::parse_bytes(oct_str.as_bytes(), 8)
423 .ok_or_else(|| MappingParseError::InvalidOctal(token.to_string()))?;
424 let bits = num.bits() as u32;
427 return Ok((VariableValue::BigUint(num), bits));
428 }
429
430 if cleaned.starts_with("0x") || cleaned.starts_with("0X") {
432 let hex_str = &cleaned[2..];
433 let num = BigUint::parse_bytes(hex_str.as_bytes(), 16)
434 .ok_or_else(|| MappingParseError::InvalidHex(token.to_string()))?;
435 let bits = num.bits() as u32;
438 return Ok((VariableValue::BigUint(num), bits));
439 }
440
441 if let Some(num) = BigUint::parse_bytes(cleaned.as_bytes(), 10) {
443 let bits = num.bits() as u32;
444 return Ok((VariableValue::BigUint(num), bits));
445 }
446
447 Err(MappingParseError::InvalidStringCharacters {
448 value: token.to_string(),
449 })
450}
451
452fn parse_key_with_kind(token: &str) -> Result<(VariableValue, ValueKind, u32), MappingParseError> {
454 if let Some(bracket_pos) = token.find('[')
456 && let Some(close_bracket) = token.find(']')
457 && close_bracket > bracket_pos
458 {
459 let value_part = &token[..bracket_pos];
460 let kind_part = &token[bracket_pos + 1..close_bracket];
461
462 let (value, len) = parse_key_value(value_part)?;
463 let kind = parse_color_kind(kind_part)?;
464
465 return Ok((value, kind, len));
466 }
467
468 let (value, len) = parse_key_value(token)?;
470 Ok((value, ValueKind::Normal, len))
471}
472
473fn parse_color_kind(token: &str) -> Result<ValueKind, MappingParseError> {
480 let hex_str = token.strip_prefix('#').unwrap_or(token);
482
483 if hex_str.len() == 6 && hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
484 let r = u8::from_str_radix(&hex_str[0..2], 16)
485 .map_err(|_| MappingParseError::InvalidHex(token.to_string()))?;
486 let g = u8::from_str_radix(&hex_str[2..4], 16)
487 .map_err(|_| MappingParseError::InvalidHex(token.to_string()))?;
488 let b = u8::from_str_radix(&hex_str[4..6], 16)
489 .map_err(|_| MappingParseError::InvalidHex(token.to_string()))?;
490 return Ok(ValueKind::Custom(Color32::from_rgb(r, g, b)));
491 }
492
493 let lower = token.to_lowercase();
495 kind_color_keywords()
496 .get(lower.as_str())
497 .copied()
498 .ok_or_else(|| MappingParseError::UnknownKindColor(token.to_string()))
499}
500
501fn parse_specifier(line: &str, keyword: &str) -> Option<String> {
503 if !(line.len() >= keyword.len() && line[..keyword.len()].eq_ignore_ascii_case(keyword)) {
505 return None;
506 }
507
508 let after_keyword = &line[keyword.len()..];
509
510 if let Some(rest) = after_keyword.trim_start().strip_prefix('=') {
512 let value = rest.trim().to_string();
514 return Some(value);
515 }
516
517 None
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523 use camino::Utf8PathBuf;
524 use num::BigUint;
525
526 fn color_eq(a: Color32, b: Color32) -> bool {
527 a.r() == b.r() && a.g() == b.g() && a.b() == b.b() && a.a() == b.a()
528 }
529
530 #[test]
531 fn parse_empty_content_uses_default_name_and_no_bits() {
532 let map = parse_content_with_default_name("", Some("DefaultName".to_string())).unwrap();
533 assert_eq!(map.name, "DefaultName".to_string());
534 assert_eq!(map.bits, 0); assert!(map.entries.is_empty());
536 }
537
538 #[test]
539 fn parse_with_name_bits_and_entries_binary_and_hex_and_colors() {
540 let content = "Name = MyMap\nBits = 4\n0[red] ZERO\n1[#00FF00] ONE\n0xA[blue] TEN";
541 let map = parse_content_with_default_name(content, None).unwrap();
542 assert_eq!(map.name, "MyMap".to_string());
543 assert_eq!(map.bits, 4);
544 let zero = map
546 .entries
547 .get(&VariableValue::BigUint(BigUint::from(0u32)))
548 .expect("ZERO entry");
549 assert_eq!(zero.label, "ZERO");
550 assert_eq!(zero.kind, ValueKind::Custom(Color32::RED));
551 let one = map
552 .entries
553 .get(&VariableValue::BigUint(BigUint::from(1u32)))
554 .expect("ONE entry");
555 assert_eq!(one.label, "ONE");
556 assert_eq!(one.kind, ValueKind::Custom(Color32::GREEN));
558 let ten = map
559 .entries
560 .get(&VariableValue::BigUint(BigUint::from(10u32)))
561 .expect("TEN entry");
562 assert_eq!(ten.label, "TEN");
563 assert_eq!(ten.kind, ValueKind::Custom(Color32::BLUE));
564 }
565
566 #[test]
567 fn duplicate_keys_keep_first_and_log() {
568 let content = "Bits = 4\n0[red] ZERO\n0[blue] ZERO_DUP\n1[green] ONE";
569 let map = parse_content_with_default_name(content, None).unwrap();
570 assert_eq!(
572 map.entries
573 .get(&VariableValue::BigUint(BigUint::from(0u32)))
574 .unwrap()
575 .label,
576 "ZERO"
577 );
578 assert_eq!(
579 map.entries
580 .get(&VariableValue::BigUint(BigUint::from(1u32)))
581 .unwrap()
582 .label,
583 "ONE"
584 );
585 assert_eq!(map.entries.len(), 2);
586 }
587
588 #[test]
589 fn infer_bit_width_from_longest_entry() {
590 let content = "3 THREE\n15 FIFTEEN"; let map = parse_content_with_default_name(content, Some("Numbers".to_string())).unwrap();
593 assert_eq!(map.bits, 4);
594 assert!(
595 map.entries
596 .contains_key(&VariableValue::BigUint(BigUint::from(3u32)))
597 );
598 assert!(
599 map.entries
600 .contains_key(&VariableValue::BigUint(BigUint::from(15u32)))
601 );
602 }
603
604 #[test]
605 fn error_on_mismatched_string_length() {
606 let content = "Bits = 3\n0bxxuu LABEL";
608 let err = parse_content_with_default_name(content, None).unwrap_err();
609 match err {
610 MappingParseError::StringLengthMismatch { expected, .. } => {
611 assert_eq!(expected, 3)
612 }
613 other => panic!("Unexpected error variant: {other}"),
614 }
615 }
616
617 #[test]
618 fn parse_line_allows_unquoted_label_with_spaces() {
619 let line = "0b0101 Label With Spaces";
620 let (val, label, kind, len) = parse_line(line).expect("parse line");
621 assert_eq!(label, "Label With Spaces");
622 assert_eq!(len, 4);
623 assert!(matches!(kind, ValueKind::Normal));
624 match val {
625 VariableValue::BigUint(v) => assert_eq!(v, BigUint::from(0b0101u32)),
626 other => panic!("unexpected value parsed: {other:?}"),
627 }
628 }
629
630 #[test]
631 fn parse_hex_and_decimal_numbers() {
632 let content = "Bits = 5\n0x1F[blue] HEXVAL\n7[green] DECVAL"; let map = parse_content_with_default_name(content, None).unwrap();
634 assert_eq!(map.bits, 5);
635 assert!(
636 map.entries
637 .contains_key(&VariableValue::BigUint(BigUint::from(31u32)))
638 );
639 assert!(
640 map.entries
641 .contains_key(&VariableValue::BigUint(BigUint::from(7u32)))
642 );
643 assert_eq!(
644 map.entries
645 .get(&VariableValue::BigUint(BigUint::from(31u32)))
646 .unwrap()
647 .label,
648 "HEXVAL"
649 );
650 assert_eq!(
651 map.entries
652 .get(&VariableValue::BigUint(BigUint::from(7u32)))
653 .unwrap()
654 .label,
655 "DECVAL"
656 );
657 }
658
659 #[test]
660 fn mix_binary_and_decimal_without_bits_line_infers_width() {
661 let content = "0b0101 BINLABEL\n7 DECSEVEN\n13 DECTHIRTEEN"; let map = parse_content_with_default_name(content, Some("Mixed".to_string())).unwrap();
664 assert_eq!(map.name, "Mixed".to_string());
665 assert_eq!(map.bits, 4);
666 assert!(
667 map.entries
668 .contains_key(&VariableValue::BigUint(BigUint::from(5u32)))
669 ); assert!(
671 map.entries
672 .contains_key(&VariableValue::BigUint(BigUint::from(7u32)))
673 ); assert!(
675 map.entries
676 .contains_key(&VariableValue::BigUint(BigUint::from(13u32)))
677 ); assert_eq!(
679 map.entries
680 .get(&VariableValue::BigUint(BigUint::from(5u32)))
681 .unwrap()
682 .label,
683 "BINLABEL"
684 );
685 assert_eq!(
686 map.entries
687 .get(&VariableValue::BigUint(BigUint::from(7u32)))
688 .unwrap()
689 .label,
690 "DECSEVEN"
691 );
692 assert_eq!(
693 map.entries
694 .get(&VariableValue::BigUint(BigUint::from(13u32)))
695 .unwrap()
696 .label,
697 "DECTHIRTEEN"
698 );
699 }
700
701 #[test]
702 fn parse_binary_octal_hex_decimal_prefixes() {
703 let content =
705 "Bits = 8\n0b1111[red] BINARY\n0o17[green] OCTAL\n0xFF[blue] HEX\n255[yellow] DECIMAL";
706 let map = parse_content_with_default_name(content, None).unwrap();
707 assert_eq!(map.bits, 8);
708 assert!(
710 map.entries
711 .contains_key(&VariableValue::BigUint(BigUint::from(15u32)))
712 );
713 assert_eq!(
714 map.entries
715 .get(&VariableValue::BigUint(BigUint::from(15u32)))
716 .unwrap()
717 .label,
718 "BINARY"
719 );
720 assert_eq!(
722 map.entries
723 .get(&VariableValue::BigUint(BigUint::from(15u32)))
724 .unwrap()
725 .label,
726 "BINARY"
727 );
728 assert!(
730 map.entries
731 .contains_key(&VariableValue::BigUint(BigUint::from(255u32)))
732 );
733 assert_eq!(
734 map.entries
735 .get(&VariableValue::BigUint(BigUint::from(255u32)))
736 .unwrap()
737 .label,
738 "HEX"
739 );
740 assert_eq!(
742 map.entries
743 .get(&VariableValue::BigUint(BigUint::from(255u32)))
744 .unwrap()
745 .label,
746 "HEX"
747 );
748 }
749
750 #[test]
751 fn file_based_translator_basic_translate_and_fallback() {
752 let temp_file = tempfile::NamedTempFile::new().expect("create temp file");
754 let path = Utf8PathBuf::from_path_buf(temp_file.path().to_path_buf())
755 .expect("temp path must be UTF-8");
756
757 let content = "Name = ExampleMapping\nBits = 4\n0[red] ZERO\n1[green] ONE\n2[blue] TWO";
758 std::fs::write(path.as_std_path(), content).expect("write mapping file");
759
760 let translator = MappingTranslator::new_from_file(&path).expect("create translator");
761 assert_eq!(translator.name(), "ExampleMapping");
762
763 let (label_one, kind_one) =
765 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(1u32)));
766 assert_eq!(label_one, "ONE");
767 match kind_one {
768 ValueKind::Custom(c) => assert!(color_eq(c, Color32::GREEN)),
769 _ => panic!("expected custom green"),
770 }
771
772 let (label_two, kind_two) =
774 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(2u32)));
775 assert_eq!(label_two, "TWO");
776 match kind_two {
777 ValueKind::Custom(c) => assert!(color_eq(c, Color32::BLUE)),
778 _ => panic!("expected custom blue"),
779 }
780
781 let (label_unknown, kind_unknown) =
783 translator.basic_translate(4, &VariableValue::String("0011".into()));
784 assert_eq!(label_unknown, "0011");
785 assert!(matches!(kind_unknown, ValueKind::Normal));
786
787 let (label_undef, kind_undef) =
789 translator.basic_translate(4, &VariableValue::String("00x1".into()));
790 assert_eq!(label_undef, "00x1");
791 assert!(matches!(kind_undef, ValueKind::Undef));
792
793 let (label_undef, kind_undef) =
795 translator.basic_translate(4, &VariableValue::String("00u1".into()));
796 assert_eq!(label_undef, "00u1");
797 assert!(matches!(kind_undef, ValueKind::Undef));
798 }
799
800 #[test]
801 fn filename_derived_name_when_no_name_line_present() {
802 let temp_file = tempfile::Builder::new()
804 .suffix(".mapping")
805 .prefix("auto_file_name_")
806 .tempfile()
807 .expect("create temp file");
808 let path = Utf8PathBuf::from_path_buf(temp_file.path().to_path_buf())
809 .expect("temp path must be UTF-8");
810
811 let content = "Bits=3\n0[red] ZERO\n1[green] ONE\n2[blue] TWO";
813 let stem = path
814 .file_stem()
815 .expect("temp file should have stem")
816 .to_string();
817 std::fs::write(path.as_std_path(), content).expect("write mapping file");
818 let translator = MappingTranslator::new_from_file(&path).expect("create translator");
819 assert_eq!(translator.name(), stem); assert_eq!(translator.map.bits, 3);
821 let (label_two, kind_two) =
823 translator.basic_translate(3, &VariableValue::BigUint(BigUint::from(2u32)));
824 assert_eq!(label_two, "TWO");
825 match kind_two {
826 ValueKind::Custom(c) => assert!(color_eq(c, Color32::BLUE)),
827 _ => panic!("expected custom blue"),
828 }
829
830 let (fallback, vk_fb) = translator.basic_translate(3, &VariableValue::String("111".into()));
832 assert_eq!(fallback, "111");
833 assert!(matches!(vk_fb, ValueKind::Normal));
834
835 let (high_imp_val, high_imp_kind) =
837 translator.basic_translate(3, &VariableValue::String("1z1".into()));
838 assert_eq!(high_imp_val, "1z1");
839 assert!(matches!(high_imp_kind, ValueKind::HighImp));
840 }
841
842 #[test]
843 fn comments_and_blank_lines_are_ignored() {
844 let content = "# top comment\n # another comment\n\nName=Commented\n# mid\nBits = 3\n# entry comment\n0[red] ZERO\n# Hash comment\n\n1[green] ONE\n # trailing comment line\n2[blue] TWO";
845 let map = parse_content_with_default_name(content, None).unwrap();
846 assert_eq!(map.name, "Commented".to_string());
847 assert_eq!(map.bits, 3);
848 assert_eq!(map.entries.len(), 3);
849 assert_eq!(
850 map.entries
851 .get(&VariableValue::BigUint(BigUint::from(0u32)))
852 .unwrap()
853 .label,
854 "ZERO"
855 );
856 assert_eq!(
857 map.entries
858 .get(&VariableValue::BigUint(BigUint::from(1u32)))
859 .unwrap()
860 .label,
861 "ONE"
862 );
863 assert_eq!(
864 map.entries
865 .get(&VariableValue::BigUint(BigUint::from(2u32)))
866 .unwrap()
867 .label,
868 "TWO"
869 );
870 }
871
872 #[test]
873 fn parse_and_translate_valuekind_keywords() {
874 let temp_file = tempfile::NamedTempFile::new().expect("create temp file");
876 let path = Utf8PathBuf::from_path_buf(temp_file.path().to_path_buf())
877 .expect("temp path must be UTF-8");
878
879 let content = "Bits = 4\n0[warn] ZERO\n1[undef] ONE\n2[highimp] TWO\n3[dontcare] THREE\n4[weak] FOUR\n5[error] FIVE\n6[normal] SIX";
881 std::fs::write(path.as_std_path(), content).expect("write mapping file");
882
883 let translator = MappingTranslator::new_from_file(&path).expect("create translator");
884 assert_eq!(translator.map.bits, 4);
885
886 let (label_zero, kind_zero) =
888 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(0u32)));
889 assert_eq!(label_zero, "ZERO");
890 assert!(matches!(kind_zero, ValueKind::Warn));
891
892 let (label_one, kind_one) =
893 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(1u32)));
894 assert_eq!(label_one, "ONE");
895 assert!(matches!(kind_one, ValueKind::Undef));
896
897 let (label_two, kind_two) =
898 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(2u32)));
899 assert_eq!(label_two, "TWO");
900 assert!(matches!(kind_two, ValueKind::HighImp));
901
902 let (label_three, kind_three) =
903 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(3u32)));
904 assert_eq!(label_three, "THREE");
905 assert!(matches!(kind_three, ValueKind::DontCare));
906
907 let (label_four, kind_four) =
908 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(4u32)));
909 assert_eq!(label_four, "FOUR");
910 assert!(matches!(kind_four, ValueKind::Weak));
911
912 let (label_five, kind_five) =
913 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(5u32)));
914 assert_eq!(label_five, "FIVE");
915 assert!(matches!(kind_five, ValueKind::Error));
916
917 let (label_six, kind_six) =
918 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(6u32)));
919 assert_eq!(label_six, "SIX");
920 assert!(matches!(kind_six, ValueKind::Normal));
921 }
922
923 #[test]
924 fn error_variants_coverage() {
925 match parse_key_value("0xZZ") {
927 Err(MappingParseError::InvalidHex(tok)) => assert_eq!(tok, "0xZZ"),
928 other => panic!("expected InvalidHex, got: {:?}", other),
929 }
930
931 match parse_color_kind("not_a_color") {
933 Err(MappingParseError::UnknownKindColor(s)) => assert_eq!(s, "not_a_color"),
934 other => panic!("expected UnknownColor, got: {:?}", other),
935 }
936
937 match normalize_first_column(&VariableValue::String("1111".into()), 4, 3) {
939 Err(MappingParseError::BinaryTooWide {
940 value,
941 required,
942 specified,
943 }) => {
944 assert_eq!(value, "1111");
945 assert!(required > specified);
946 }
947 other => panic!("expected BinaryTooWide, got: {:?}", other),
948 }
949
950 match normalize_first_column(&VariableValue::String("ABCD".into()), 4, 3) {
952 Err(MappingParseError::StringLengthMismatch {
953 value,
954 value_len,
955 expected,
956 }) => {
957 assert_eq!(value, "ABCD");
958 assert_eq!(value_len, 4);
959 assert_eq!(expected, 3);
960 }
961 other => panic!("expected StringLengthMismatch, got: {:?}", other),
962 }
963
964 match parse_line("") {
966 Err(MappingParseError::EmptyLine) => {}
967 other => panic!("expected EmptyLine, got: {:?}", other),
968 }
969
970 match parse_line("0101") {
971 Err(MappingParseError::MissingMapping) => {}
972 other => panic!("expected MissingSecondColumn, got: {:?}", other),
973 }
974
975 let content = "Bits = 4\n0 ZERO red\nBADLINE";
977 match parse_content_with_default_name(content, None) {
978 Err(MappingParseError::LineError {
979 line,
980 content,
981 message: _,
982 }) => {
983 assert_eq!(line, 3);
984 assert_eq!(content, "BADLINE");
985 }
986 other => panic!("expected LineError, got: {:?}", other),
987 }
988
989 let pathbuf = std::path::PathBuf::from("/this/path/should/not/exist/mapping.tmp");
991 let path = Utf8PathBuf::from_path_buf(pathbuf).expect("path must be UTF-8");
992 match MappingTranslatorMap::new(path) {
993 Err(MappingParseError::Io(_)) => {}
994 other => panic!("expected Io, got: {:?}", other),
995 }
996 }
997}