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;
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    // Span must be finite for Y-axis scaling
62    (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/// 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            .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                // we can consistently fall back to the default here since sub-fields
662                // are never checked for their preferred translator
663                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    /// Flattens the translation result into path, value pairs
686    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        // FIXME for consistency we should not fall back to `translators.default` here, but fetch the
695        // preferred translator - but doing that ATM will break if the spade translator is used, since
696        // on the first render the spade translator seems to not have loaded its information yet.
697        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        // Check that we have some translators
843        assert!(!translators.all_translator_names().is_empty());
844
845        // Check default translator exists
846        assert!(
847            translators
848                .all_translator_names()
849                .contains(&translators.default.as_str())
850        );
851
852        // Check we can get a translator by name
853        let hex_translator = translators.get_translator("Hexadecimal");
854        assert_eq!(hex_translator.name(), "Hexadecimal");
855
856        // Check basic translator names subset
857        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}