1use std::collections::HashMap;
2#[cfg(not(target_arch = "wasm32"))]
3use std::path::Path;
4use std::sync::Arc;
5use std::sync::mpsc::Sender;
6
7use ecolor::Color32;
8use eyre::Result;
9#[cfg(not(target_arch = "wasm32"))]
10use toml::Table;
11#[cfg(not(target_arch = "wasm32"))]
12use tracing::warn;
13
14mod basic_translators;
15pub mod clock;
16mod color_translators;
17mod enum_translator;
18mod event_translator;
19mod fixed_point;
20mod instruction_translators;
21mod mapping_translators;
22pub mod numeric_translators;
23#[cfg(feature = "python")]
24mod python_translators;
25#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
26pub mod wasm_translator;
27
28pub use basic_translators::*;
29use clock::ClockTranslator;
30use event_translator::EventTranslator;
31#[cfg(not(target_arch = "wasm32"))]
32use instruction_decoder::Decoder;
33pub use instruction_translators::*;
34use itertools::Itertools;
35pub use numeric_translators::*;
36use surfer_translation_types::{
37 BasicTranslator, HierFormatResult, SubFieldFlatTranslationResult, TranslatedValue,
38 TranslationPreference, TranslationResult, Translator, ValueKind, ValueRepr, VariableEncoding,
39 VariableInfo, VariableValue,
40};
41
42use crate::config::SurferTheme;
43use crate::translation::enum_translator::EnumTranslator;
44use crate::wave_container::{ScopeId, VarId};
45use crate::{message::Message, wave_container::VariableMeta};
46
47pub type DynTranslator = dyn Translator<VarId, ScopeId, Message>;
48pub type DynBasicTranslator = dyn BasicTranslator<VarId, ScopeId>;
49
50#[cfg(not(target_arch = "wasm32"))]
51static DECODERS_DIR: &str = "decoders";
52
53#[cfg(not(target_arch = "wasm32"))]
54static MAPPINGS_DIR: &str = "mappings";
55
56fn translate_with_basic(
57 t: &DynBasicTranslator,
58 variable: &VariableMeta,
59 value: &VariableValue,
60) -> Result<TranslationResult> {
61 let (val, kind) = t.basic_translate(variable.num_bits.unwrap_or(0), value);
62 Ok(TranslationResult {
63 val: ValueRepr::String(val),
64 kind,
65 subfields: vec![],
66 })
67}
68
69#[derive(Clone)]
70pub enum AnyTranslator {
71 Full(Arc<DynTranslator>),
72 Basic(Arc<DynBasicTranslator>),
73 #[cfg(feature = "python")]
74 Python(Arc<python_translators::PythonTranslator>),
75}
76
77impl AnyTranslator {
78 #[must_use]
79 pub fn is_basic(&self) -> bool {
80 matches!(self, AnyTranslator::Basic(_))
81 }
82}
83
84impl Translator<VarId, ScopeId, Message> for AnyTranslator {
85 fn name(&self) -> String {
86 match self {
87 AnyTranslator::Full(t) => t.name(),
88 AnyTranslator::Basic(t) => t.name(),
89 #[cfg(feature = "python")]
90 AnyTranslator::Python(t) => t.name(),
91 }
92 }
93
94 fn set_wave_source(&self, wave_source: Option<surfer_translation_types::WaveSource>) {
95 match self {
96 AnyTranslator::Full(translator) => translator.set_wave_source(wave_source),
97 AnyTranslator::Basic(_) => {}
98 #[cfg(feature = "python")]
99 AnyTranslator::Python(_) => {}
100 }
101 }
102
103 fn translate(
104 &self,
105 variable: &VariableMeta,
106 value: &VariableValue,
107 ) -> Result<TranslationResult> {
108 match self {
109 AnyTranslator::Full(t) => t.translate(variable, value),
110 AnyTranslator::Basic(t) => translate_with_basic(&**t, variable, value),
111 #[cfg(feature = "python")]
112 AnyTranslator::Python(t) => translate_with_basic(&**t, variable, value),
113 }
114 }
115
116 fn variable_info(&self, variable: &VariableMeta) -> Result<VariableInfo> {
117 match self {
118 AnyTranslator::Full(t) => t.variable_info(variable),
119 AnyTranslator::Basic(t) => t.variable_info(variable),
120 #[cfg(feature = "python")]
121 #[cfg(target_family = "unix")]
122 AnyTranslator::Python(t) => t.variable_info(variable),
123 }
124 }
125
126 fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
127 match self {
128 AnyTranslator::Full(t) => t.translates(variable),
129 AnyTranslator::Basic(t) => t.translates(variable),
130 #[cfg(feature = "python")]
131 AnyTranslator::Python(t) => t.translates(variable),
132 }
133 }
134
135 fn reload(&self, sender: Sender<Message>) {
136 match self {
137 AnyTranslator::Full(t) => t.reload(sender),
138 AnyTranslator::Basic(_) => (),
139 #[cfg(feature = "python")]
140 AnyTranslator::Python(_) => (),
141 }
142 }
143
144 fn variable_name_info(
145 &self,
146 variable: &surfer_translation_types::VariableMeta<VarId, ScopeId>,
147 ) -> Option<surfer_translation_types::translator::VariableNameInfo> {
148 match self {
149 AnyTranslator::Full(translator) => translator.variable_name_info(variable),
150 AnyTranslator::Basic(_) => None,
151 #[cfg(feature = "python")]
152 AnyTranslator::Python(_) => None,
153 }
154 }
155
156 fn translate_numeric(&self, variable: &VariableMeta, value: &VariableValue) -> Option<f64> {
157 match self {
158 AnyTranslator::Full(t) => t.translate_numeric(variable, value),
159 AnyTranslator::Basic(t) => {
160 t.basic_translate_numeric(variable.num_bits.unwrap_or(0), value)
161 }
162 #[cfg(feature = "python")]
163 AnyTranslator::Python(t) => {
164 t.basic_translate_numeric(variable.num_bits.unwrap_or(0), value)
165 }
166 }
167 }
168}
169
170#[cfg(not(target_arch = "wasm32"))]
175fn find_user_decoders() -> Vec<Arc<DynBasicTranslator>> {
176 let mut decoders: Vec<Arc<DynBasicTranslator>> = vec![];
177 if let Some(proj_dirs) = crate::config::PROJECT_DIR.as_ref() {
178 let mut config_decoders = find_user_decoders_at_path(proj_dirs.config_dir());
179 decoders.append(&mut config_decoders);
180 }
181
182 let mut project_decoders = find_user_decoders_at_path(Path::new(crate::config::LOCAL_DIR));
183 decoders.append(&mut project_decoders);
184
185 decoders
186}
187
188#[cfg(not(target_arch = "wasm32"))]
190fn find_user_decoders_at_path(path: &Path) -> Vec<Arc<DynBasicTranslator>> {
191 use tracing::{error, info};
192
193 let mut decoders: Vec<Arc<DynBasicTranslator>> = vec![];
194 let p = path.join(DECODERS_DIR);
195 info!("Looking for user decoders at {}", p.display());
196 let Ok(decoder_dirs) = std::fs::read_dir(p) else {
197 return decoders;
198 };
199
200 for decoder_dir in decoder_dirs.flatten() {
201 if decoder_dir.metadata().is_ok_and(|m| m.is_dir()) {
202 let Ok(name) = decoder_dir.file_name().into_string() else {
203 warn!("Cannot load decoder. Invalid name.");
204 continue;
205 };
206 let mut tomls = vec![];
207 let mut width: Option<toml::Value> = None;
210
211 if let Ok(toml_files) = std::fs::read_dir(decoder_dir.path()) {
212 for toml_file in toml_files.flatten() {
213 if toml_file
214 .file_name()
215 .into_string()
216 .is_ok_and(|file_name| file_name.ends_with(".toml"))
217 {
218 let Ok(text) = std::fs::read_to_string(toml_file.path()) else {
219 warn!(
220 "Skipping toml file {}. Cannot read file.",
221 toml_file.path().display()
222 );
223 continue;
224 };
225
226 let Ok(toml_parsed) = text.parse::<Table>() else {
227 warn!(
228 "Skipping toml file {}. Cannot parse toml.",
229 toml_file.path().display()
230 );
231 continue;
232 };
233
234 let Some(toml_width) = toml_parsed.get("width") else {
235 warn!(
236 "Skipping toml file {}. Mandatory key 'width' is missing.",
237 toml_file.path().display()
238 );
239 continue;
240 };
241
242 if width.clone().is_some_and(|width| width != *toml_width) {
243 warn!(
244 "Skipping toml file {}. Bit widths do not match.",
245 toml_file.path().display()
246 );
247 continue;
248 }
249 width = Some(toml_width.clone());
250
251 tomls.push(toml_parsed);
252 }
253 }
254 }
255
256 if let Some(width) = width.and_then(|width| width.as_integer()) {
257 match Decoder::new_from_table(tomls) {
258 Ok(decoder) => {
259 let translator = InstructionTranslator {
260 name,
261 decoder,
262 num_bits: width.unsigned_abs() as u32,
263 };
264 tracing::info!(
265 "Loaded {}-bit instruction decoder: {} ",
266 width.unsigned_abs(),
267 translator.name(),
268 );
269 decoders.push(Arc::new(translator));
270 }
271 Err(e) => {
272 error!("Error while building decoder {name}");
273 for toml in e {
274 for error in toml {
275 error!("{error}");
276 }
277 }
278 }
279 }
280 }
281 }
282 }
283 decoders
284}
285
286#[cfg(not(target_arch = "wasm32"))]
289fn find_user_mapping_translators() -> Vec<Arc<DynBasicTranslator>> {
290 let mut translators: Vec<Arc<DynBasicTranslator>> = vec![];
291 if let Some(proj_dirs) = &*crate::config::PROJECT_DIR {
292 let mut config_decoders = find_user_mapping_translators_at_path(proj_dirs.config_dir());
293 translators.append(&mut config_decoders);
294 }
295
296 let mut project_decoders =
297 find_user_mapping_translators_at_path(Path::new(crate::config::LOCAL_DIR));
298 translators.append(&mut project_decoders);
299
300 translators
301}
302
303#[cfg(not(target_arch = "wasm32"))]
305fn find_user_mapping_translators_at_path(path: &Path) -> Vec<Arc<DynBasicTranslator>> {
306 let mut mapping_translators: Vec<Arc<DynBasicTranslator>> = vec![];
307 let p = path.join(MAPPINGS_DIR);
308 tracing::info!("Looking for user mapping translators at {}", p.display());
309 let Ok(mapping_files) = std::fs::read_dir(p) else {
310 return mapping_translators;
311 };
312
313 use crate::translation::mapping_translators::MappingTranslator;
314 for mapping_file in mapping_files.flatten() {
315 tracing::info!(
316 "Found user mapping translator file: {}",
317 mapping_file.path().display()
318 );
319 let Ok(utf8_path) = camino::Utf8PathBuf::try_from(mapping_file.path()) else {
320 warn!(
321 "Cannot load mapping translator. Path is not valid UTF-8: {}",
322 mapping_file.path().display()
323 );
324 continue;
325 };
326 let translator = MappingTranslator::new_from_file(utf8_path);
327 match translator {
328 Err(e) => {
329 warn!(
330 "Cannot load mapping translator from file {}: {}",
331 mapping_file.path().display(),
332 e
333 );
334 continue;
335 }
336 Ok(translator) => {
337 tracing::info!(
338 "Loaded {:?}-bit(s) mapping translator: {}",
339 translator.bits(),
340 translator.name(),
341 );
342 mapping_translators.push(Arc::new(translator));
343 }
344 };
345 }
346 mapping_translators
347}
348
349#[must_use]
350pub fn all_translators() -> TranslatorList {
351 #[allow(unused_mut)]
353 let mut basic_translators: Vec<Arc<DynBasicTranslator>> = vec![
354 Arc::new(BitTranslator {}),
355 Arc::new(HexTranslator {}),
356 Arc::new(OctalTranslator {}),
357 Arc::new(GroupingBinaryTranslator {}),
358 Arc::new(BinaryTranslator {}),
359 Arc::new(ASCIITranslator {}),
360 Arc::new(new_rv32_translator()),
361 Arc::new(new_rv64_translator()),
362 Arc::new(new_mips_translator()),
363 Arc::new(new_la64_translator()),
364 Arc::new(LebTranslator {}),
365 Arc::new(UnsignedTranslator {}),
366 Arc::new(SignedTranslator {}),
367 Arc::new(SinglePrecisionTranslator {}),
368 Arc::new(DoublePrecisionTranslator {}),
369 Arc::new(HalfPrecisionTranslator {}),
370 Arc::new(BFloat16Translator {}),
371 Arc::new(Posit32Translator {}),
372 Arc::new(Posit16Translator {}),
373 Arc::new(Posit8Translator {}),
374 Arc::new(PositQuire8Translator {}),
375 Arc::new(PositQuire16Translator {}),
376 Arc::new(E5M2Translator {}),
377 Arc::new(E4M3Translator {}),
378 Arc::new(NumberOfOnesTranslator {}),
379 Arc::new(LeadingOnesTranslator {}),
380 Arc::new(TrailingOnesTranslator {}),
381 Arc::new(LeadingZerosTranslator {}),
382 Arc::new(TrailingZerosTranslator {}),
383 Arc::new(IdenticalMSBsTranslator {}),
384 #[cfg(feature = "f128")]
385 Arc::new(QuadPrecisionTranslator {}),
386 Arc::new(color_translators::RGBTranslator {}),
387 Arc::new(color_translators::GrayScaleTranslator {}),
388 Arc::new(color_translators::YCbCrTranslator {}),
389 ];
390
391 #[cfg(not(target_arch = "wasm32"))]
392 basic_translators.append(&mut find_user_decoders());
393
394 #[cfg(not(target_arch = "wasm32"))]
395 basic_translators.append(&mut find_user_mapping_translators());
396
397 TranslatorList::new(
398 basic_translators,
399 vec![
400 Arc::new(ClockTranslator::new()),
401 Arc::new(StringTranslator {}),
402 Arc::new(EnumTranslator {}),
403 Arc::new(UnsignedFixedPointTranslator),
404 Arc::new(SignedFixedPointTranslator),
405 Arc::new(EventTranslator {}),
406 ],
407 )
408}
409
410#[derive(Default)]
411pub struct TranslatorList {
412 inner: HashMap<String, AnyTranslator>,
413 #[cfg(feature = "python")]
414 python_translator: Option<(camino::Utf8PathBuf, String, AnyTranslator)>,
415 pub default: String,
416}
417
418impl TranslatorList {
419 #[must_use]
420 pub fn new(basic: Vec<Arc<DynBasicTranslator>>, translators: Vec<Arc<DynTranslator>>) -> Self {
421 Self {
422 default: "Hexadecimal".to_string(),
423 inner: basic
424 .into_iter()
425 .map(|t| (t.name(), AnyTranslator::Basic(t)))
426 .chain(
427 translators
428 .into_iter()
429 .map(|t| (t.name(), AnyTranslator::Full(t))),
430 )
431 .collect(),
432 #[cfg(feature = "python")]
433 python_translator: None,
434 }
435 }
436
437 pub fn all_translator_names(&self) -> Vec<&str> {
438 #[cfg(feature = "python")]
439 let python_name = self
440 .python_translator
441 .as_ref()
442 .map(|(_, name, _)| name.as_str());
443 #[cfg(not(feature = "python"))]
444 let python_name = None;
445 self.inner
446 .keys()
447 .map(String::as_str)
448 .chain(python_name)
449 .collect()
450 }
451
452 #[must_use]
453 pub fn all_translators(&self) -> Vec<&AnyTranslator> {
454 #[cfg(feature = "python")]
455 let python_translator = self.python_translator.as_ref().map(|(_, _, t)| t);
456 #[cfg(not(feature = "python"))]
457 let python_translator = None;
458 self.inner.values().chain(python_translator).collect()
459 }
460
461 #[must_use]
462 pub fn basic_translator_names(&self) -> Vec<&str> {
463 self.inner
464 .iter()
465 .filter_map(|(name, t)| t.is_basic().then_some(name.as_str()))
466 .collect()
467 }
468
469 #[must_use]
470 pub fn get_translator(&self, name: &str) -> &AnyTranslator {
471 #[cfg(feature = "python")]
472 let python_translator = || {
473 self.python_translator
474 .as_ref()
475 .filter(|(_, python_name, _)| python_name == name)
476 .map(|(_, _, t)| t)
477 };
478 #[cfg(not(feature = "python"))]
479 let python_translator = || None;
480 self.inner
481 .get(name)
482 .or_else(python_translator)
483 .unwrap_or_else(|| panic!("No translator called {name}"))
484 }
485
486 #[must_use]
487 pub fn clone_translator(&self, name: &str) -> AnyTranslator {
488 self.get_translator(name).clone()
489 }
490
491 pub fn add_or_replace(&mut self, t: AnyTranslator) {
492 self.inner.insert(t.name(), t);
493 }
494
495 #[must_use]
496 pub fn is_valid_translator(&self, meta: &VariableMeta, candidate: &str) -> bool {
497 self.get_translator(candidate)
498 .translates(meta)
499 .map(|preference| preference != TranslationPreference::No)
500 .unwrap_or(false)
501 }
502
503 #[cfg(feature = "python")]
504 pub fn load_python_translator(&mut self, filename: camino::Utf8PathBuf) -> Result<()> {
505 tracing::debug!("Reading Python code from disk: {filename}");
506 let code = std::ffi::CString::new(std::fs::read_to_string(&filename)?)?;
507 let mut translators = python_translators::PythonTranslator::new(&code.as_c_str())?;
508 if translators.len() != 1 {
509 eyre::bail!("Only one Python translator per file is supported for now");
510 }
511 let translator = translators.pop().unwrap();
512 self.python_translator = Some((
513 filename,
514 translator.name(),
515 AnyTranslator::Python(Arc::new(translator)),
516 ));
517 Ok(())
518 }
519
520 #[cfg(feature = "python")]
521 pub fn has_python_translator(&self) -> bool {
522 self.python_translator.is_some()
523 }
524
525 #[cfg(feature = "python")]
526 pub fn reload_python_translator(&mut self) -> Result<()> {
527 if let Some((path, _, _)) = self.python_translator.take() {
528 self.load_python_translator(path)?;
529 }
530 Ok(())
531 }
532}
533
534fn format(
535 val: &ValueRepr,
536 kind: ValueKind,
537 subtranslator_name: &String,
538 translators: &TranslatorList,
539 subresults: &[HierFormatResult],
540) -> Option<TranslatedValue> {
541 match val {
542 ValueRepr::Bit(val) => {
543 let AnyTranslator::Basic(subtranslator) =
544 translators.get_translator(subtranslator_name)
545 else {
546 panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
547 };
548
549 Some(TranslatedValue::from_basic_translate(
550 subtranslator.basic_translate(1, &VariableValue::String(val.to_string())),
551 ))
552 }
553 ValueRepr::Bits(bit_count, bits) => {
554 let AnyTranslator::Basic(subtranslator) =
555 translators.get_translator(subtranslator_name)
556 else {
557 panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
558 };
559
560 Some(TranslatedValue::from_basic_translate(
561 subtranslator.basic_translate(*bit_count, &VariableValue::String(bits.clone())),
562 ))
563 }
564 ValueRepr::String(sval) => Some(TranslatedValue {
565 value: sval.clone(),
566 kind,
567 }),
568 ValueRepr::Tuple => Some(TranslatedValue {
569 value: format!(
570 "({})",
571 subresults
572 .iter()
573 .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
574 .join(", ")
575 ),
576 kind,
577 }),
578 ValueRepr::Struct => Some(TranslatedValue {
579 value: format!(
580 "{{{}}}",
581 subresults
582 .iter()
583 .map(|v| {
584 let n = v.names.join("_");
585 format!("{n}: {}", v.this.as_ref().map_or("-", |t| t.value.as_str()))
586 })
587 .join(", ")
588 ),
589 kind,
590 }),
591 ValueRepr::Array => Some(TranslatedValue {
592 value: format!(
593 "[{}]",
594 subresults
595 .iter()
596 .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
597 .join(", ")
598 ),
599 kind,
600 }),
601 ValueRepr::NotPresent => None,
602 ValueRepr::Enum { idx, name } => Some(TranslatedValue {
603 value: format!(
604 "{name}{{{}}}",
605 subresults[*idx]
606 .this
607 .as_ref()
608 .map_or("-", |t| t.value.as_str())
609 ),
610 kind,
611 }),
612 ValueRepr::Event => Some(TranslatedValue {
613 value: "Event".to_string(),
614 kind,
615 }),
616 }
617}
618
619#[local_impl::local_impl]
620impl TranslationResultExt for TranslationResult {
621 fn sub_format(
622 &self,
623 formats: &[crate::displayed_item::FieldFormat],
624 translators: &TranslatorList,
625 path_so_far: &[String],
626 ) -> Vec<HierFormatResult> {
627 self.subfields
628 .iter()
629 .map(|res| {
630 let sub_path = path_so_far
631 .iter()
632 .chain([&res.name])
633 .cloned()
634 .collect::<Vec<_>>();
635
636 let sub = res.result.sub_format(formats, translators, &sub_path);
637
638 let translator_name = formats
641 .iter()
642 .find(|e| e.field == sub_path)
643 .map(|e| e.format.clone())
644 .unwrap_or(translators.default.clone());
645 let formatted = format(
646 &res.result.val,
647 res.result.kind,
648 &translator_name,
649 translators,
650 &sub,
651 );
652
653 HierFormatResult {
654 this: formatted,
655 names: sub_path,
656 fields: sub,
657 }
658 })
659 .collect::<Vec<_>>()
660 }
661
662 fn format_flat(
664 &self,
665 root_format: &Option<String>,
666 formats: &[crate::displayed_item::FieldFormat],
667 translators: &TranslatorList,
668 ) -> Vec<SubFieldFlatTranslationResult> {
669 let sub_result = self.sub_format(formats, translators, &[]);
670
671 let formatted = format(
675 &self.val,
676 self.kind,
677 root_format.as_ref().unwrap_or(&translators.default),
678 translators,
679 &sub_result,
680 );
681
682 let formatted = HierFormatResult {
683 names: vec![],
684 this: formatted,
685 fields: sub_result,
686 };
687 let mut collected = vec![];
688 formatted.collect_into(&mut collected);
689 collected
690 }
691}
692
693#[local_impl::local_impl]
694impl VariableInfoExt for VariableInfo {
695 fn get_subinfo(&self, path: &[String]) -> &VariableInfo {
696 match path {
697 [] => self,
698 [field, rest @ ..] => match self {
699 VariableInfo::Compound { subfields } => subfields
700 .iter()
701 .find(|(f, _)| f == field)
702 .unwrap()
703 .1
704 .get_subinfo(rest),
705 VariableInfo::Bits => panic!(),
706 VariableInfo::Bool => panic!(),
707 VariableInfo::Clock => panic!(),
708 VariableInfo::String => panic!(),
709 VariableInfo::Real => panic!(),
710 VariableInfo::Event => panic!(),
711 },
712 }
713 }
714
715 fn has_subpath(&self, path: &[String]) -> bool {
716 match path {
717 [] => true,
718 [field, rest @ ..] => match self {
719 VariableInfo::Compound { subfields } => subfields
720 .iter()
721 .find(|&(f, _)| f == field)
722 .is_some_and(|(_, info)| info.has_subpath(rest)),
723 _ => false,
724 },
725 }
726 }
727}
728
729#[local_impl::local_impl]
730impl ValueKindExt for ValueKind {
731 fn color(&self, user_color: Color32, theme: &SurferTheme) -> Color32 {
732 match self {
733 ValueKind::HighImp => theme.variable_highimp,
734 ValueKind::Undef => theme.variable_undef,
735 ValueKind::DontCare => theme.variable_dontcare,
736 ValueKind::Warn => theme.variable_undef,
737 ValueKind::Custom(custom_color) => *custom_color,
738 ValueKind::Weak => theme.variable_weak,
739 ValueKind::Error => theme.accent_error.background,
740 ValueKind::Normal => user_color,
741 ValueKind::Event => theme.variable_event,
742 }
743 }
744}
745
746pub struct StringTranslator {}
747
748impl Translator<VarId, ScopeId, Message> for StringTranslator {
749 fn name(&self) -> String {
750 "String".to_string()
751 }
752
753 fn translate(
754 &self,
755 _variable: &VariableMeta,
756 value: &VariableValue,
757 ) -> Result<TranslationResult> {
758 match value {
759 VariableValue::BigUint(b) => Ok(TranslationResult {
760 val: ValueRepr::String(format!("ERROR (0x{b:x})")),
761 kind: ValueKind::Warn,
762 subfields: vec![],
763 }),
764 VariableValue::String(s) => Ok(TranslationResult {
765 val: ValueRepr::String((*s).to_string()),
766 kind: ValueKind::Normal,
767 subfields: vec![],
768 }),
769 }
770 }
771
772 fn variable_info(&self, _variable: &VariableMeta) -> Result<VariableInfo> {
773 Ok(VariableInfo::String)
774 }
775
776 fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
777 if variable.encoding == VariableEncoding::String {
778 Ok(TranslationPreference::Prefer)
779 } else {
780 Ok(TranslationPreference::No)
781 }
782 }
783}
784
785fn check_single_wordlength(num_bits: Option<u32>, required: u32) -> Result<TranslationPreference> {
786 if Some(required) == num_bits {
787 Ok(TranslationPreference::Yes)
788 } else {
789 Ok(TranslationPreference::No)
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796
797 #[test]
798 fn check_single_wordlength_exact_match() {
799 let result = check_single_wordlength(Some(32), 32).unwrap();
800 assert_eq!(result, TranslationPreference::Yes);
801 }
802
803 #[test]
804 fn check_single_wordlength_mismatch() {
805 let result = check_single_wordlength(Some(64), 32).unwrap();
806 assert_eq!(result, TranslationPreference::No);
807 }
808
809 #[test]
810 fn check_single_wordlength_none() {
811 let result = check_single_wordlength(None, 32).unwrap();
812 assert_eq!(result, TranslationPreference::No);
813 }
814
815 #[test]
816 fn translator_list_basic_operations() {
817 let translators = all_translators();
818
819 assert!(!translators.all_translator_names().is_empty());
821
822 assert!(
824 translators
825 .all_translator_names()
826 .contains(&translators.default.as_str())
827 );
828
829 let hex_translator = translators.get_translator("Hexadecimal");
831 assert_eq!(hex_translator.name(), "Hexadecimal");
832
833 let basic_names = translators.basic_translator_names();
835 assert!(basic_names.contains(&"Hexadecimal"));
836 assert!(basic_names.contains(&"Binary"));
837 }
838
839 #[test]
840 fn variable_info_has_subpath() {
841 use surfer_translation_types::VariableInfo;
842
843 let info = VariableInfo::Compound {
844 subfields: vec![
845 ("field1".to_string(), VariableInfo::Bits),
846 (
847 "field2".to_string(),
848 VariableInfo::Compound {
849 subfields: vec![("nested".to_string(), VariableInfo::Bool)],
850 },
851 ),
852 ],
853 };
854
855 assert!(info.has_subpath(&[]));
856 assert!(info.has_subpath(&["field1".to_string()]));
857 assert!(info.has_subpath(&["field2".to_string()]));
858 assert!(info.has_subpath(&["field2".to_string(), "nested".to_string()]));
859 assert!(!info.has_subpath(&["nonexistent".to_string()]));
860 assert!(!info.has_subpath(&["field2".to_string(), "nonexistent".to_string()]));
861 }
862}