Skip to main content

libsurfer/translation/
mod.rs

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;
31pub(crate) use color_translators::ycbcr_to_rgb;
32use event_translator::EventTranslator;
33#[cfg(not(target_arch = "wasm32"))]
34use instruction_decoder::Decoder;
35pub use instruction_translators::*;
36use itertools::Itertools;
37pub use numeric_translators::*;
38use surfer_translation_types::{
39    BasicTranslator, HierFormatResult, NumericRange, SubFieldFlatTranslationResult,
40    TranslatedValue, TranslationPreference, TranslationResult, Translator, ValueKind, ValueRepr,
41    VariableEncoding, VariableInfo, VariableValue,
42};
43
44use crate::config::SurferTheme;
45use crate::translation::enum_translator::EnumTranslator;
46use crate::wave_container::{ScopeId, VarId};
47use crate::{message::Message, wave_container::VariableMeta};
48
49pub type DynTranslator = dyn Translator<VarId, ScopeId, Message>;
50pub type DynBasicTranslator = dyn BasicTranslator<VarId, ScopeId>;
51
52fn integer_numeric_range(num_bits: u32, signed: bool) -> Option<NumericRange> {
53    if num_bits == 0 || num_bits >= f64::MAX_EXP as u32 {
54        return None;
55    }
56    let (min, max) = if signed {
57        let half = 2.0f64.powi((num_bits - 1).cast_signed());
58        (-half, half - 1.0)
59    } else {
60        (0.0, 2.0f64.powi(num_bits.cast_signed()) - 1.0)
61    };
62    // Span must be finite for Y-axis scaling
63    (max - min).is_finite().then_some(NumericRange { min, max })
64}
65
66#[cfg(not(target_arch = "wasm32"))]
67static DECODERS_DIR: &str = "decoders";
68
69#[cfg(not(target_arch = "wasm32"))]
70static MAPPINGS_DIR: &str = "mappings";
71
72fn translate_with_basic(
73    t: &DynBasicTranslator,
74    variable: &VariableMeta,
75    value: &VariableValue,
76) -> Result<TranslationResult> {
77    let (val, kind) = t.basic_translate(variable.num_bits.unwrap_or(0), value);
78    Ok(TranslationResult {
79        val: ValueRepr::String(val),
80        kind,
81        subfields: vec![],
82    })
83}
84
85#[derive(Clone)]
86pub enum AnyTranslator {
87    Full(Arc<DynTranslator>),
88    Basic(Arc<DynBasicTranslator>),
89    #[cfg(feature = "python")]
90    Python(Arc<python_translators::PythonTranslator>),
91}
92
93impl AnyTranslator {
94    #[must_use]
95    pub fn is_basic(&self) -> bool {
96        matches!(self, AnyTranslator::Basic(_))
97    }
98}
99
100impl Translator<VarId, ScopeId, Message> for AnyTranslator {
101    fn name(&self) -> String {
102        match self {
103            AnyTranslator::Full(t) => t.name(),
104            AnyTranslator::Basic(t) => t.name(),
105            #[cfg(feature = "python")]
106            AnyTranslator::Python(t) => t.name(),
107        }
108    }
109
110    fn set_wave_source(&self, wave_source: Option<surfer_translation_types::WaveSource>) {
111        match self {
112            AnyTranslator::Full(translator) => translator.set_wave_source(wave_source),
113            AnyTranslator::Basic(_) => {}
114            #[cfg(feature = "python")]
115            AnyTranslator::Python(_) => {}
116        }
117    }
118
119    fn translate(
120        &self,
121        variable: &VariableMeta,
122        value: &VariableValue,
123    ) -> Result<TranslationResult> {
124        match self {
125            AnyTranslator::Full(t) => t.translate(variable, value),
126            AnyTranslator::Basic(t) => translate_with_basic(&**t, variable, value),
127            #[cfg(feature = "python")]
128            AnyTranslator::Python(t) => translate_with_basic(&**t, variable, value),
129        }
130    }
131
132    fn variable_info(&self, variable: &VariableMeta) -> Result<VariableInfo> {
133        match self {
134            AnyTranslator::Full(t) => t.variable_info(variable),
135            AnyTranslator::Basic(t) => t.variable_info(variable),
136            #[cfg(feature = "python")]
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/// Look inside the config directory and inside "$(cwd)/.surfer" for user-defined decoders
195/// To add a new decoder named 'x', add a directory 'x' to the decoders directory
196/// Inside, multiple toml files can be added which will all be used for decoding 'x'
197/// This is useful e.g., for layering RISC-V extensions
198#[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/// Look for user defined decoders in path.
213#[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            // Keeps track of the bit width of the first parsed toml
232            // All tomls must use the same width
233            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/// Look inside the config directory and inside "$(cwd)/.surfer" for user-defined mapping translators
311/// To add a new mapping translator named 'x', add a file 'x' to the mapping translators directory
312#[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/// Look for user defined mapping translators in path.
328#[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    // WASM does not need mut, non-wasm does so we'll allow it
375    #[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            .is_ok_and(|preference| preference != TranslationPreference::No)
523    }
524
525    #[cfg(feature = "python")]
526    pub fn load_python_translator(&mut self, filename: camino::Utf8PathBuf) -> Result<()> {
527        tracing::debug!("Reading Python code from disk: {filename}");
528        let code = std::ffi::CString::new(std::fs::read_to_string(&filename)?)?;
529        let mut translators = python_translators::PythonTranslator::new(&code.as_c_str())?;
530        if translators.len() != 1 {
531            eyre::bail!("Only one Python translator per file is supported for now");
532        }
533        let translator = translators.pop().unwrap();
534        self.python_translator = Some((
535            filename,
536            translator.name(),
537            AnyTranslator::Python(Arc::new(translator)),
538        ));
539        Ok(())
540    }
541
542    #[cfg(feature = "python")]
543    pub fn has_python_translator(&self) -> bool {
544        self.python_translator.is_some()
545    }
546
547    #[cfg(feature = "python")]
548    pub fn reload_python_translator(&mut self) -> Result<()> {
549        if let Some((path, _, _)) = self.python_translator.take() {
550            self.load_python_translator(path)?;
551        }
552        Ok(())
553    }
554}
555
556fn format(
557    val: &ValueRepr,
558    kind: ValueKind,
559    subtranslator_name: &String,
560    translators: &TranslatorList,
561    subresults: &[HierFormatResult],
562) -> Option<TranslatedValue> {
563    match val {
564        ValueRepr::Bit(val) => {
565            let AnyTranslator::Basic(subtranslator) =
566                translators.get_translator(subtranslator_name)
567            else {
568                panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
569            };
570
571            Some(TranslatedValue::from_basic_translate(
572                subtranslator.basic_translate(1, &VariableValue::String(val.to_string())),
573            ))
574        }
575        ValueRepr::Bits(bit_count, bits) => {
576            let AnyTranslator::Basic(subtranslator) =
577                translators.get_translator(subtranslator_name)
578            else {
579                panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
580            };
581
582            Some(TranslatedValue::from_basic_translate(
583                subtranslator.basic_translate(*bit_count, &VariableValue::String(bits.clone())),
584            ))
585        }
586        ValueRepr::String(sval) => Some(TranslatedValue {
587            value: sval.clone(),
588            kind,
589        }),
590        ValueRepr::Tuple => Some(TranslatedValue {
591            value: format!(
592                "({})",
593                subresults
594                    .iter()
595                    .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
596                    .join(", ")
597            ),
598            kind,
599        }),
600        ValueRepr::Struct => Some(TranslatedValue {
601            value: format!(
602                "{{{}}}",
603                subresults
604                    .iter()
605                    .map(|v| {
606                        let n = v.names.join("_");
607                        format!("{n}: {}", v.this.as_ref().map_or("-", |t| t.value.as_str()))
608                    })
609                    .join(", ")
610            ),
611            kind,
612        }),
613        ValueRepr::Array => Some(TranslatedValue {
614            value: format!(
615                "[{}]",
616                subresults
617                    .iter()
618                    .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
619                    .join(", ")
620            ),
621            kind,
622        }),
623        ValueRepr::NotPresent => None,
624        ValueRepr::Enum { idx, name } => Some(TranslatedValue {
625            value: format!(
626                "{name}{{{}}}",
627                subresults[*idx]
628                    .this
629                    .as_ref()
630                    .map_or("-", |t| t.value.as_str())
631            ),
632            kind,
633        }),
634        ValueRepr::Event => Some(TranslatedValue {
635            value: "Event".to_string(),
636            kind,
637        }),
638    }
639}
640
641#[local_impl::local_impl]
642impl TranslationResultExt for TranslationResult {
643    fn sub_format(
644        &self,
645        formats: &[crate::displayed_item::FieldFormat],
646        translators: &TranslatorList,
647        path_so_far: &[String],
648    ) -> Vec<HierFormatResult> {
649        self.subfields
650            .iter()
651            .map(|res| {
652                let sub_path = path_so_far
653                    .iter()
654                    .chain([&res.name])
655                    .cloned()
656                    .collect::<Vec<_>>();
657
658                let sub = res.result.sub_format(formats, translators, &sub_path);
659
660                // we can consistently fall back to the default here since sub-fields
661                // are never checked for their preferred translator
662                let translator_name = formats
663                    .iter()
664                    .find(|e| e.field == sub_path)
665                    .map(|e| e.format.clone())
666                    .unwrap_or(translators.default.clone());
667                let formatted = format(
668                    &res.result.val,
669                    res.result.kind,
670                    &translator_name,
671                    translators,
672                    &sub,
673                );
674
675                HierFormatResult {
676                    this: formatted,
677                    names: sub_path,
678                    fields: sub,
679                }
680            })
681            .collect::<Vec<_>>()
682    }
683
684    /// Flattens the translation result into path, value pairs
685    fn format_flat(
686        &self,
687        root_format: &Option<String>,
688        formats: &[crate::displayed_item::FieldFormat],
689        translators: &TranslatorList,
690    ) -> Vec<SubFieldFlatTranslationResult> {
691        let sub_result = self.sub_format(formats, translators, &[]);
692
693        // FIXME for consistency we should not fall back to `translators.default` here, but fetch the
694        // preferred translator - but doing that ATM will break if the spade translator is used, since
695        // on the first render the spade translator seems to not have loaded its information yet.
696        let formatted = format(
697            &self.val,
698            self.kind,
699            root_format.as_ref().unwrap_or(&translators.default),
700            translators,
701            &sub_result,
702        );
703
704        let formatted = HierFormatResult {
705            names: vec![],
706            this: formatted,
707            fields: sub_result,
708        };
709        let mut collected = vec![];
710        formatted.collect_into(&mut collected);
711        collected
712    }
713}
714
715#[local_impl::local_impl]
716impl VariableInfoExt for VariableInfo {
717    fn get_subinfo(&self, path: &[String]) -> &VariableInfo {
718        match path {
719            [] => self,
720            [field, rest @ ..] => match self {
721                VariableInfo::Compound { subfields } => subfields
722                    .iter()
723                    .find(|(f, _)| f == field)
724                    .unwrap()
725                    .1
726                    .get_subinfo(rest),
727                VariableInfo::Bits => panic!(),
728                VariableInfo::Bool => panic!(),
729                VariableInfo::Clock => panic!(),
730                VariableInfo::String => panic!(),
731                VariableInfo::Real => panic!(),
732                VariableInfo::Event => panic!(),
733            },
734        }
735    }
736
737    fn has_subpath(&self, path: &[String]) -> bool {
738        match path {
739            [] => true,
740            [field, rest @ ..] => match self {
741                VariableInfo::Compound { subfields } => subfields
742                    .iter()
743                    .find(|&(f, _)| f == field)
744                    .is_some_and(|(_, info)| info.has_subpath(rest)),
745                _ => false,
746            },
747        }
748    }
749}
750
751#[local_impl::local_impl]
752impl ValueKindExt for ValueKind {
753    fn color(&self, user_color: Color32, theme: &SurferTheme) -> Color32 {
754        match self {
755            ValueKind::HighImp => theme.variable_highimp,
756            ValueKind::Undef => theme.variable_undef,
757            ValueKind::DontCare => theme.variable_dontcare,
758            ValueKind::Warn => theme.variable_undef,
759            ValueKind::Custom(custom_color) => *custom_color,
760            ValueKind::Weak => theme.variable_weak,
761            ValueKind::Error => theme.accent_error.background,
762            ValueKind::Normal => user_color,
763            ValueKind::Event => theme.variable_event,
764        }
765    }
766}
767
768pub struct StringTranslator {}
769
770impl Translator<VarId, ScopeId, Message> for StringTranslator {
771    fn name(&self) -> String {
772        "String".to_string()
773    }
774
775    fn translate(
776        &self,
777        _variable: &VariableMeta,
778        value: &VariableValue,
779    ) -> Result<TranslationResult> {
780        match value {
781            VariableValue::BigUint(b) => Ok(TranslationResult {
782                val: ValueRepr::String(format!("ERROR (0x{b:x})")),
783                kind: ValueKind::Warn,
784                subfields: vec![],
785            }),
786            VariableValue::String(s) => Ok(TranslationResult {
787                val: ValueRepr::String((*s).clone()),
788                kind: ValueKind::Normal,
789                subfields: vec![],
790            }),
791        }
792    }
793
794    fn variable_info(&self, _variable: &VariableMeta) -> Result<VariableInfo> {
795        Ok(VariableInfo::String)
796    }
797
798    fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
799        if variable.encoding == VariableEncoding::String {
800            Ok(TranslationPreference::Prefer)
801        } else {
802            Ok(TranslationPreference::No)
803        }
804    }
805}
806
807fn check_single_wordlength(num_bits: Option<u32>, required: u32) -> Result<TranslationPreference> {
808    if Some(required) == num_bits {
809        Ok(TranslationPreference::Yes)
810    } else {
811        Ok(TranslationPreference::No)
812    }
813}
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818
819    #[test]
820    fn check_single_wordlength_exact_match() {
821        let result = check_single_wordlength(Some(32), 32).unwrap();
822        assert_eq!(result, TranslationPreference::Yes);
823    }
824
825    #[test]
826    fn check_single_wordlength_mismatch() {
827        let result = check_single_wordlength(Some(64), 32).unwrap();
828        assert_eq!(result, TranslationPreference::No);
829    }
830
831    #[test]
832    fn check_single_wordlength_none() {
833        let result = check_single_wordlength(None, 32).unwrap();
834        assert_eq!(result, TranslationPreference::No);
835    }
836
837    #[test]
838    fn translator_list_basic_operations() {
839        let translators = all_translators();
840
841        // Check that we have some translators
842        assert!(!translators.all_translator_names().is_empty());
843
844        // Check default translator exists
845        assert!(
846            translators
847                .all_translator_names()
848                .contains(&translators.default.as_str())
849        );
850
851        // Check we can get a translator by name
852        let hex_translator = translators.get_translator("Hexadecimal");
853        assert_eq!(hex_translator.name(), "Hexadecimal");
854
855        // Check basic translator names subset
856        let basic_names = translators.basic_translator_names();
857        assert!(basic_names.contains(&"Hexadecimal"));
858        assert!(basic_names.contains(&"Binary"));
859    }
860
861    #[test]
862    fn variable_info_has_subpath() {
863        use surfer_translation_types::VariableInfo;
864
865        let info = VariableInfo::Compound {
866            subfields: vec![
867                ("field1".to_string(), VariableInfo::Bits),
868                (
869                    "field2".to_string(),
870                    VariableInfo::Compound {
871                        subfields: vec![("nested".to_string(), VariableInfo::Bool)],
872                    },
873                ),
874            ],
875        };
876
877        assert!(info.has_subpath(&[]));
878        assert!(info.has_subpath(&["field1".to_string()]));
879        assert!(info.has_subpath(&["field2".to_string()]));
880        assert!(info.has_subpath(&["field2".to_string(), "nested".to_string()]));
881        assert!(!info.has_subpath(&["nonexistent".to_string()]));
882        assert!(!info.has_subpath(&["field2".to_string(), "nonexistent".to_string()]));
883    }
884}