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;
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/// Look inside the config directory and inside "$(cwd)/.surfer" for user-defined decoders
171/// To add a new decoder named 'x', add a directory 'x' to the decoders directory
172/// Inside, multiple toml files can be added which will all be used for decoding 'x'
173/// This is useful e.g., for layering RISC-V extensions
174#[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/// Look for user defined decoders in path.
189#[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            // Keeps track of the bit width of the first parsed toml
208            // All tomls must use the same width
209            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/// Look inside the config directory and inside "$(cwd)/.surfer" for user-defined mapping translators
287/// To add a new mapping translator named 'x', add a file 'x' to the mapping translators directory
288#[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/// Look for user defined mapping translators in path.
304#[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    // WASM does not need mut, non-wasm does so we'll allow it
352    #[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                // we can consistently fall back to the default here since sub-fields
639                // are never checked for their preferred translator
640                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    /// Flattens the translation result into path, value pairs
663    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        // FIXME for consistency we should not fall back to `translators.default` here, but fetch the
672        // preferred translator - but doing that ATM will break if the spade translator is used, since
673        // on the first render the spade translator seems to not have loaded its information yet.
674        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        // Check that we have some translators
820        assert!(!translators.all_translator_names().is_empty());
821
822        // Check default translator exists
823        assert!(
824            translators
825                .all_translator_names()
826                .contains(&translators.default.as_str())
827        );
828
829        // Check we can get a translator by name
830        let hex_translator = translators.get_translator("Hexadecimal");
831        assert_eq!(hex_translator.name(), "Hexadecimal");
832
833        // Check basic translator names subset
834        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}