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