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.to_string()))?;
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.to_string(),
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.to_string(),
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 } else {
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.to_string()), 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 use std::time::{SystemTime, UNIX_EPOCH};
753 let ts = SystemTime::now()
755 .duration_since(UNIX_EPOCH)
756 .unwrap()
757 .as_nanos();
758 let mut pathbuf = std::env::temp_dir();
759 pathbuf.push(format!("mapping_test_{}.txt", ts));
760 let path = Utf8PathBuf::from_path_buf(pathbuf).expect("temp path must be UTF-8");
761
762 let content = "Name = ExampleMapping\nBits = 4\n0[red] ZERO\n1[green] ONE\n2[blue] TWO";
763 std::fs::write(path.as_std_path(), content).expect("write mapping file");
764
765 let translator = MappingTranslator::new_from_file(&path)
766 .ok()
767 .expect("create translator");
768 assert_eq!(translator.name(), "ExampleMapping");
769
770 let (label_one, kind_one) =
772 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(1u32)));
773 assert_eq!(label_one, "ONE");
774 match kind_one {
775 ValueKind::Custom(c) => assert!(color_eq(c, Color32::GREEN)),
776 _ => panic!("expected custom green"),
777 }
778
779 let (label_two, kind_two) =
781 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(2u32)));
782 assert_eq!(label_two, "TWO");
783 match kind_two {
784 ValueKind::Custom(c) => assert!(color_eq(c, Color32::BLUE)),
785 _ => panic!("expected custom blue"),
786 }
787
788 let (label_unknown, kind_unknown) =
790 translator.basic_translate(4, &VariableValue::String("0011".into()));
791 assert_eq!(label_unknown, "0011");
792 assert!(matches!(kind_unknown, ValueKind::Normal));
793
794 let (label_undef, kind_undef) =
796 translator.basic_translate(4, &VariableValue::String("00x1".into()));
797 assert_eq!(label_undef, "00x1");
798 assert!(matches!(kind_undef, ValueKind::Undef));
799
800 let (label_undef, kind_undef) =
802 translator.basic_translate(4, &VariableValue::String("00u1".into()));
803 assert_eq!(label_undef, "00u1");
804 assert!(matches!(kind_undef, ValueKind::Undef));
805 }
806
807 #[test]
808 fn filename_derived_name_when_no_name_line_present() {
809 use std::time::{SystemTime, UNIX_EPOCH};
810 let ts = SystemTime::now()
811 .duration_since(UNIX_EPOCH)
812 .unwrap()
813 .as_micros();
814 let stem = format!("auto_file_name_{}", ts);
815 let mut pathbuf = std::env::temp_dir();
816 pathbuf.push(format!("{stem}.mapping"));
817
818 let content = "Bits=3\n0[red] ZERO\n1[green] ONE\n2[blue] TWO";
820 let path = Utf8PathBuf::from_path_buf(pathbuf).expect("temp path must be UTF-8");
821 std::fs::write(path.as_std_path(), content).expect("write mapping file");
822 let translator = MappingTranslator::new_from_file(&path)
823 .ok()
824 .expect("create translator");
825 assert_eq!(translator.name(), stem); assert_eq!(translator.map.bits, 3);
827 let (label_two, kind_two) =
829 translator.basic_translate(3, &VariableValue::BigUint(BigUint::from(2u32)));
830 assert_eq!(label_two, "TWO");
831 match kind_two {
832 ValueKind::Custom(c) => assert!(color_eq(c, Color32::BLUE)),
833 _ => panic!("expected custom blue"),
834 }
835
836 let (fallback, vk_fb) = translator.basic_translate(3, &VariableValue::String("111".into()));
838 assert_eq!(fallback, "111");
839 assert!(matches!(vk_fb, ValueKind::Normal));
840
841 let (high_imp_val, high_imp_kind) =
843 translator.basic_translate(3, &VariableValue::String("1z1".into()));
844 assert_eq!(high_imp_val, "1z1");
845 assert!(matches!(high_imp_kind, ValueKind::HighImp));
846 }
847
848 #[test]
849 fn comments_and_blank_lines_are_ignored() {
850 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";
851 let map = parse_content_with_default_name(content, None).unwrap();
852 assert_eq!(map.name, "Commented".to_string());
853 assert_eq!(map.bits, 3);
854 assert_eq!(map.entries.len(), 3);
855 assert_eq!(
856 map.entries
857 .get(&VariableValue::BigUint(BigUint::from(0u32)))
858 .unwrap()
859 .label,
860 "ZERO"
861 );
862 assert_eq!(
863 map.entries
864 .get(&VariableValue::BigUint(BigUint::from(1u32)))
865 .unwrap()
866 .label,
867 "ONE"
868 );
869 assert_eq!(
870 map.entries
871 .get(&VariableValue::BigUint(BigUint::from(2u32)))
872 .unwrap()
873 .label,
874 "TWO"
875 );
876 }
877
878 #[test]
879 fn parse_and_translate_valuekind_keywords() {
880 use std::time::{SystemTime, UNIX_EPOCH};
881
882 let ts = SystemTime::now()
883 .duration_since(UNIX_EPOCH)
884 .unwrap()
885 .as_nanos();
886 let mut pathbuf = std::env::temp_dir();
887 pathbuf.push(format!("mapping_kinds_{}.txt", ts));
888 let path = Utf8PathBuf::from_path_buf(pathbuf).expect("temp path must be UTF-8");
889
890 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";
892 std::fs::write(path.as_std_path(), content).expect("write mapping file");
893
894 let translator = MappingTranslator::new_from_file(&path)
895 .ok()
896 .expect("create translator");
897 assert_eq!(translator.map.bits, 4);
898
899 let (label_zero, kind_zero) =
901 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(0u32)));
902 assert_eq!(label_zero, "ZERO");
903 assert!(matches!(kind_zero, ValueKind::Warn));
904
905 let (label_one, kind_one) =
906 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(1u32)));
907 assert_eq!(label_one, "ONE");
908 assert!(matches!(kind_one, ValueKind::Undef));
909
910 let (label_two, kind_two) =
911 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(2u32)));
912 assert_eq!(label_two, "TWO");
913 assert!(matches!(kind_two, ValueKind::HighImp));
914
915 let (label_three, kind_three) =
916 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(3u32)));
917 assert_eq!(label_three, "THREE");
918 assert!(matches!(kind_three, ValueKind::DontCare));
919
920 let (label_four, kind_four) =
921 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(4u32)));
922 assert_eq!(label_four, "FOUR");
923 assert!(matches!(kind_four, ValueKind::Weak));
924
925 let (label_five, kind_five) =
926 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(5u32)));
927 assert_eq!(label_five, "FIVE");
928 assert!(matches!(kind_five, ValueKind::Error));
929
930 let (label_six, kind_six) =
931 translator.basic_translate(4, &VariableValue::BigUint(BigUint::from(6u32)));
932 assert_eq!(label_six, "SIX");
933 assert!(matches!(kind_six, ValueKind::Normal));
934 }
935
936 #[test]
937 fn error_variants_coverage() {
938 match parse_key_value("0xZZ") {
940 Err(MappingParseError::InvalidHex(tok)) => assert_eq!(tok, "0xZZ"),
941 other => panic!("expected InvalidHex, got: {:?}", other),
942 }
943
944 match parse_color_kind("not_a_color") {
946 Err(MappingParseError::UnknownKindColor(s)) => assert_eq!(s, "not_a_color"),
947 other => panic!("expected UnknownColor, got: {:?}", other),
948 }
949
950 match normalize_first_column(&VariableValue::String("1111".into()), 4, 3) {
952 Err(MappingParseError::BinaryTooWide {
953 value,
954 required,
955 specified,
956 }) => {
957 assert_eq!(value, "1111");
958 assert!(required > specified);
959 }
960 other => panic!("expected BinaryTooWide, got: {:?}", other),
961 }
962
963 match normalize_first_column(&VariableValue::String("ABCD".into()), 4, 3) {
965 Err(MappingParseError::StringLengthMismatch {
966 value,
967 value_len,
968 expected,
969 }) => {
970 assert_eq!(value, "ABCD");
971 assert_eq!(value_len, 4);
972 assert_eq!(expected, 3);
973 }
974 other => panic!("expected StringLengthMismatch, got: {:?}", other),
975 }
976
977 match parse_line("") {
979 Err(MappingParseError::EmptyLine) => {}
980 other => panic!("expected EmptyLine, got: {:?}", other),
981 }
982
983 match parse_line("0101") {
984 Err(MappingParseError::MissingMapping) => {}
985 other => panic!("expected MissingSecondColumn, got: {:?}", other),
986 }
987
988 let content = "Bits = 4\n0 ZERO red\nBADLINE";
990 match parse_content_with_default_name(content, None) {
991 Err(MappingParseError::LineError {
992 line,
993 content,
994 message: _,
995 }) => {
996 assert_eq!(line, 3);
997 assert_eq!(content, "BADLINE");
998 }
999 other => panic!("expected LineError, got: {:?}", other),
1000 }
1001
1002 let pathbuf = std::path::PathBuf::from("/this/path/should/not/exist/mapping.tmp");
1004 let path = Utf8PathBuf::from_path_buf(pathbuf).expect("path must be UTF-8");
1005 match MappingTranslatorMap::new(path) {
1006 Err(MappingParseError::Io(_)) => {}
1007 other => panic!("expected Io, got: {:?}", other),
1008 }
1009 }
1010}