libsurfer/translation/
mod.rs

1use std::collections::HashMap;
2#[cfg(not(target_arch = "wasm32"))]
3use std::path::Path;
4use std::sync::mpsc::Sender;
5
6use color_eyre::Result;
7#[cfg(not(target_arch = "wasm32"))]
8use directories::ProjectDirs;
9use ecolor::Color32;
10#[cfg(not(target_arch = "wasm32"))]
11use log::warn;
12#[cfg(not(target_arch = "wasm32"))]
13use toml::Table;
14
15mod basic_translators;
16pub mod clock;
17mod enum_translator;
18mod fixed_point;
19mod instruction_translators;
20pub mod numeric_translators;
21#[cfg(feature = "python")]
22mod python_translators;
23#[cfg(not(target_arch = "wasm32"))]
24pub mod wasm_translator;
25
26pub use basic_translators::*;
27use clock::ClockTranslator;
28#[cfg(not(target_arch = "wasm32"))]
29use instruction_decoder::Decoder;
30pub use instruction_translators::*;
31use itertools::Itertools;
32pub use numeric_translators::*;
33use surfer_translation_types::{
34    BasicTranslator, HierFormatResult, SubFieldFlatTranslationResult, TranslatedValue,
35    TranslationPreference, TranslationResult, Translator, ValueKind, ValueRepr, VariableEncoding,
36    VariableInfo, VariableValue,
37};
38
39use crate::config::SurferTheme;
40use crate::translation::enum_translator::EnumTranslator;
41use crate::wave_container::{ScopeId, VarId};
42use crate::{message::Message, wave_container::VariableMeta};
43
44pub type DynTranslator = dyn Translator<VarId, ScopeId, Message>;
45pub type DynBasicTranslator = dyn BasicTranslator<VarId, ScopeId>;
46
47fn translate_with_basic(
48    t: &DynBasicTranslator,
49    variable: &VariableMeta,
50    value: &VariableValue,
51) -> Result<TranslationResult> {
52    let (val, kind) = t.basic_translate(variable.num_bits.unwrap_or(0) as u64, value);
53    Ok(TranslationResult {
54        val: ValueRepr::String(val),
55        kind,
56        subfields: vec![],
57    })
58}
59
60pub enum AnyTranslator {
61    Full(Box<DynTranslator>),
62    Basic(Box<DynBasicTranslator>),
63    #[cfg(feature = "python")]
64    Python(python_translators::PythonTranslator),
65}
66
67impl AnyTranslator {
68    pub fn is_basic(&self) -> bool {
69        matches!(self, AnyTranslator::Basic(_))
70    }
71}
72
73impl Translator<VarId, ScopeId, Message> for AnyTranslator {
74    fn name(&self) -> String {
75        match self {
76            AnyTranslator::Full(t) => t.name(),
77            AnyTranslator::Basic(t) => t.name(),
78            #[cfg(feature = "python")]
79            AnyTranslator::Python(t) => t.name(),
80        }
81    }
82
83    fn set_wave_source(&self, wave_source: Option<surfer_translation_types::WaveSource>) {
84        match self {
85            AnyTranslator::Full(translator) => translator.set_wave_source(wave_source),
86            AnyTranslator::Basic(_) => {}
87            #[cfg(feature = "python")]
88            AnyTranslator::Python(t) => {}
89        }
90    }
91
92    fn translate(
93        &self,
94        variable: &VariableMeta,
95        value: &VariableValue,
96    ) -> Result<TranslationResult> {
97        match self {
98            AnyTranslator::Full(t) => t.translate(variable, value),
99            AnyTranslator::Basic(t) => translate_with_basic(&**t, variable, value),
100            #[cfg(feature = "python")]
101            AnyTranslator::Python(t) => translate_with_basic(t, variable, value),
102        }
103    }
104
105    fn variable_info(&self, variable: &VariableMeta) -> Result<VariableInfo> {
106        match self {
107            AnyTranslator::Full(t) => t.variable_info(variable),
108            AnyTranslator::Basic(t) => t.variable_info(variable),
109            #[cfg(feature = "python")]
110            #[cfg(target_family = "unix")]
111            AnyTranslator::Python(t) => t.variable_info(variable),
112        }
113    }
114
115    fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
116        match self {
117            AnyTranslator::Full(t) => t.translates(variable),
118            AnyTranslator::Basic(t) => t.translates(variable),
119            #[cfg(feature = "python")]
120            AnyTranslator::Python(t) => t.translates(variable),
121        }
122    }
123
124    fn reload(&self, sender: Sender<Message>) {
125        match self {
126            AnyTranslator::Full(t) => t.reload(sender),
127            AnyTranslator::Basic(_) => (),
128            #[cfg(feature = "python")]
129            AnyTranslator::Python(_) => (),
130        }
131    }
132
133    fn variable_name_info(
134        &self,
135        variable: &surfer_translation_types::VariableMeta<VarId, ScopeId>,
136    ) -> Option<surfer_translation_types::translator::VariableNameInfo> {
137        match self {
138            AnyTranslator::Full(translator) => translator.variable_name_info(variable),
139            AnyTranslator::Basic(_) => None,
140            #[cfg(feature = "python")]
141            AnyTranslator::Python(t) => None,
142        }
143    }
144}
145
146/// Look inside the config directory and inside "$(cwd)/.surfer" for user-defined decoders
147/// To add a new decoder named 'x', add a directory 'x' to the decoders directory
148/// Inside, multiple toml files can be added which will all be used for decoding 'x'
149/// This is useful e.g., for layering RISC-V extensions
150#[cfg(not(target_arch = "wasm32"))]
151fn find_user_decoders() -> Vec<Box<DynBasicTranslator>> {
152    let mut decoders: Vec<Box<DynBasicTranslator>> = vec![];
153    if let Some(proj_dirs) = ProjectDirs::from("org", "surfer-project", "surfer") {
154        let mut config_decoders = find_user_decoders_at_path(proj_dirs.config_dir());
155        decoders.append(&mut config_decoders);
156    }
157
158    let mut project_decoders = find_user_decoders_at_path(Path::new(".surfer"));
159    decoders.append(&mut project_decoders);
160
161    decoders
162}
163
164/// Look for user defined decoders in path.
165#[cfg(not(target_arch = "wasm32"))]
166fn find_user_decoders_at_path(path: &Path) -> Vec<Box<DynBasicTranslator>> {
167    use log::error;
168
169    let mut decoders: Vec<Box<DynBasicTranslator>> = vec![];
170    let Ok(decoder_dirs) = std::fs::read_dir(path.join("decoders")) else {
171        return decoders;
172    };
173
174    for decoder_dir in decoder_dirs.flatten() {
175        if decoder_dir.metadata().is_ok_and(|m| m.is_dir()) {
176            let Ok(name) = decoder_dir.file_name().into_string() else {
177                warn!("Cannot load decoder. Invalid name.");
178                continue;
179            };
180            let mut tomls = vec![];
181            // Keeps track of the bit width of the first parsed toml
182            // All tomls must use the same width
183            let mut width: Option<toml::Value> = None;
184
185            if let Ok(toml_files) = std::fs::read_dir(decoder_dir.path()) {
186                for toml_file in toml_files.flatten() {
187                    if toml_file
188                        .file_name()
189                        .into_string()
190                        .is_ok_and(|file_name| file_name.ends_with(".toml"))
191                    {
192                        let Ok(text) = std::fs::read_to_string(toml_file.path()) else {
193                            warn!(
194                                "Skipping toml file {:?}. Cannot read file.",
195                                toml_file.path()
196                            );
197                            continue;
198                        };
199
200                        let Ok(toml_parsed) = text.parse::<Table>() else {
201                            warn!(
202                                "Skipping toml file {:?}. Cannot parse toml.",
203                                toml_file.path()
204                            );
205                            continue;
206                        };
207
208                        let Some(toml_width) = toml_parsed.get("width") else {
209                            warn!(
210                                "Skipping toml file {:?}. Mandatory key 'width' is missing.",
211                                toml_file.path()
212                            );
213                            continue;
214                        };
215
216                        if width.clone().is_some_and(|width| width != *toml_width) {
217                            warn!(
218                                "Skipping toml file {:?}. Bit widths do not match.",
219                                toml_file.path()
220                            );
221                            continue;
222                        } else {
223                            width = Some(toml_width.clone());
224                        }
225
226                        tomls.push(toml_parsed);
227                    }
228                }
229            }
230
231            if let Some(width) = width.and_then(|width| width.as_integer()) {
232                match Decoder::new_from_table(tomls) {
233                    Ok(decoder) => decoders.push(Box::new(InstructionTranslator {
234                        name,
235                        decoder,
236                        num_bits: width.unsigned_abs(),
237                    })),
238                    Err(e) => {
239                        error!("Error while building decoder {name}");
240                        for toml in e {
241                            for error in toml {
242                                error!("{error}");
243                            }
244                        }
245                    }
246                }
247            }
248        }
249    }
250    decoders
251}
252
253pub fn all_translators() -> TranslatorList {
254    // WASM does not need mut, non-wasm does so we'll allow it
255    #[allow(unused_mut)]
256    let mut basic_translators: Vec<Box<DynBasicTranslator>> = vec![
257        Box::new(BitTranslator {}),
258        Box::new(HexTranslator {}),
259        Box::new(OctalTranslator {}),
260        Box::new(GroupingBinaryTranslator {}),
261        Box::new(BinaryTranslator {}),
262        Box::new(ASCIITranslator {}),
263        Box::new(new_rv32_translator()),
264        Box::new(new_rv64_translator()),
265        Box::new(new_mips_translator()),
266        Box::new(new_la64_translator()),
267        Box::new(LebTranslator {}),
268        Box::new(UnsignedTranslator {}),
269        Box::new(SignedTranslator {}),
270        Box::new(SinglePrecisionTranslator {}),
271        Box::new(DoublePrecisionTranslator {}),
272        Box::new(HalfPrecisionTranslator {}),
273        Box::new(BFloat16Translator {}),
274        Box::new(Posit32Translator {}),
275        Box::new(Posit16Translator {}),
276        Box::new(Posit8Translator {}),
277        Box::new(PositQuire8Translator {}),
278        Box::new(PositQuire16Translator {}),
279        Box::new(E5M2Translator {}),
280        Box::new(E4M3Translator {}),
281        Box::new(NumberOfOnesTranslator {}),
282        Box::new(LeadingOnesTranslator {}),
283        Box::new(TrailingOnesTranslator {}),
284        Box::new(LeadingZerosTranslator {}),
285        Box::new(TrailingZerosTranslator {}),
286        Box::new(IdenticalMSBsTranslator {}),
287        #[cfg(feature = "f128")]
288        Box::new(QuadPrecisionTranslator {}),
289    ];
290
291    #[cfg(not(target_arch = "wasm32"))]
292    basic_translators.append(&mut find_user_decoders());
293
294    TranslatorList::new(
295        basic_translators,
296        vec![
297            Box::new(ClockTranslator::new()),
298            Box::new(StringTranslator {}),
299            Box::new(EnumTranslator {}),
300            Box::new(UnsignedFixedPointTranslator),
301            Box::new(SignedFixedPointTranslator),
302        ],
303    )
304}
305
306#[derive(Default)]
307pub struct TranslatorList {
308    inner: HashMap<String, AnyTranslator>,
309    #[cfg(feature = "python")]
310    python_translator: Option<(camino::Utf8PathBuf, String, AnyTranslator)>,
311    pub default: String,
312}
313
314impl TranslatorList {
315    pub fn new(basic: Vec<Box<DynBasicTranslator>>, translators: Vec<Box<DynTranslator>>) -> Self {
316        Self {
317            default: "Hexadecimal".to_string(),
318            inner: basic
319                .into_iter()
320                .map(|t| (t.name(), AnyTranslator::Basic(t)))
321                .chain(
322                    translators
323                        .into_iter()
324                        .map(|t| (t.name(), AnyTranslator::Full(t))),
325                )
326                .collect(),
327            #[cfg(feature = "python")]
328            python_translator: None,
329        }
330    }
331
332    pub fn all_translator_names(&self) -> Vec<&str> {
333        #[cfg(feature = "python")]
334        let python_name = self
335            .python_translator
336            .as_ref()
337            .map(|(_, name, _)| name.as_str());
338        #[cfg(not(feature = "python"))]
339        let python_name = None;
340        self.inner
341            .keys()
342            .map(String::as_str)
343            .chain(python_name)
344            .collect()
345    }
346
347    pub fn all_translators(&self) -> Vec<&AnyTranslator> {
348        #[cfg(feature = "python")]
349        let python_translator = self.python_translator.as_ref().map(|(_, _, t)| t);
350        #[cfg(not(feature = "python"))]
351        let python_translator = None;
352        self.inner.values().chain(python_translator).collect()
353    }
354
355    pub fn basic_translator_names(&self) -> Vec<&str> {
356        self.inner
357            .iter()
358            .filter_map(|(name, t)| t.is_basic().then_some(name.as_str()))
359            .collect()
360    }
361
362    pub fn get_translator(&self, name: &str) -> &AnyTranslator {
363        #[cfg(feature = "python")]
364        let python_translator = || {
365            self.python_translator
366                .as_ref()
367                .filter(|(_, python_name, _)| python_name == name)
368                .map(|(_, _, t)| t)
369        };
370        #[cfg(not(feature = "python"))]
371        let python_translator = || None;
372        self.inner
373            .get(name)
374            .or_else(python_translator)
375            .unwrap_or_else(|| panic!("No translator called {name}"))
376    }
377
378    pub fn add_or_replace(&mut self, t: AnyTranslator) {
379        self.inner.insert(t.name(), t);
380    }
381
382    pub fn is_valid_translator(&self, meta: &VariableMeta, candidate: &str) -> bool {
383        self.get_translator(candidate)
384            .translates(meta)
385            .map(|preference| preference != TranslationPreference::No)
386            .unwrap_or(false)
387    }
388
389    #[cfg(feature = "python")]
390    pub fn load_python_translator(&mut self, filename: camino::Utf8PathBuf) -> Result<()> {
391        log::debug!("Reading Python code from disk: {filename}");
392        let code = std::ffi::CString::new(std::fs::read_to_string(&filename)?)?;
393        let mut translators = python_translators::PythonTranslator::new(&code.as_c_str())?;
394        if translators.len() != 1 {
395            color_eyre::eyre::bail!("Only one Python translator per file is supported for now");
396        }
397        let translator = translators.pop().unwrap();
398        self.python_translator = Some((
399            filename,
400            translator.name(),
401            AnyTranslator::Python(translator),
402        ));
403        Ok(())
404    }
405
406    #[cfg(feature = "python")]
407    pub fn has_python_translator(&self) -> bool {
408        self.python_translator.is_some()
409    }
410
411    #[cfg(feature = "python")]
412    pub fn reload_python_translator(&mut self) -> Result<()> {
413        if let Some((path, _, _)) = self.python_translator.take() {
414            self.load_python_translator(path)?;
415        }
416        Ok(())
417    }
418}
419
420fn format(
421    val: &ValueRepr,
422    kind: ValueKind,
423    subtranslator_name: &String,
424    translators: &TranslatorList,
425    subresults: &[HierFormatResult],
426) -> Option<TranslatedValue> {
427    match val {
428        ValueRepr::Bit(val) => {
429            let AnyTranslator::Basic(subtranslator) =
430                translators.get_translator(subtranslator_name)
431            else {
432                panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
433            };
434
435            Some(TranslatedValue::from_basic_translate(
436                subtranslator.basic_translate(1, &VariableValue::String(val.to_string())),
437            ))
438        }
439        ValueRepr::Bits(bit_count, bits) => {
440            let AnyTranslator::Basic(subtranslator) =
441                translators.get_translator(subtranslator_name)
442            else {
443                panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
444            };
445
446            Some(TranslatedValue::from_basic_translate(
447                subtranslator.basic_translate(*bit_count, &VariableValue::String(bits.clone())),
448            ))
449        }
450        ValueRepr::String(sval) => Some(TranslatedValue {
451            value: sval.clone(),
452            kind,
453        }),
454        ValueRepr::Tuple => Some(TranslatedValue {
455            value: format!(
456                "({})",
457                subresults
458                    .iter()
459                    .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
460                    .join(", ")
461            ),
462            kind,
463        }),
464        ValueRepr::Struct => Some(TranslatedValue {
465            value: format!(
466                "{{{}}}",
467                subresults
468                    .iter()
469                    .map(|v| {
470                        let n = v.names.join("_");
471                        format!("{n}: {}", v.this.as_ref().map_or("-", |t| t.value.as_str()))
472                    })
473                    .join(", ")
474            ),
475            kind,
476        }),
477        ValueRepr::Array => Some(TranslatedValue {
478            value: format!(
479                "[{}]",
480                subresults
481                    .iter()
482                    .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
483                    .join(", ")
484            ),
485            kind,
486        }),
487        ValueRepr::NotPresent => None,
488        ValueRepr::Enum { idx, name } => Some(TranslatedValue {
489            value: format!(
490                "{name}{{{}}}",
491                subresults[*idx]
492                    .this
493                    .as_ref()
494                    .map_or("-", |t| t.value.as_str())
495            ),
496            kind,
497        }),
498    }
499}
500
501#[local_impl::local_impl]
502impl TranslationResultExt for TranslationResult {
503    fn sub_format(
504        &self,
505        formats: &[crate::displayed_item::FieldFormat],
506        translators: &TranslatorList,
507        path_so_far: &[String],
508    ) -> Vec<HierFormatResult> {
509        self.subfields
510            .iter()
511            .map(|res| {
512                let sub_path = path_so_far
513                    .iter()
514                    .chain([&res.name])
515                    .cloned()
516                    .collect::<Vec<_>>();
517
518                let sub = res.result.sub_format(formats, translators, &sub_path);
519
520                // we can consistently fall back to the default here since sub-fields
521                // are never checked for their preferred translator
522                let translator_name = formats
523                    .iter()
524                    .find(|e| e.field == sub_path)
525                    .map(|e| e.format.clone())
526                    .unwrap_or(translators.default.clone());
527                let formatted = format(
528                    &res.result.val,
529                    res.result.kind,
530                    &translator_name,
531                    translators,
532                    &sub,
533                );
534
535                HierFormatResult {
536                    this: formatted,
537                    names: sub_path,
538                    fields: sub,
539                }
540            })
541            .collect::<Vec<_>>()
542    }
543
544    /// Flattens the translation result into path, value pairs
545    fn format_flat(
546        &self,
547        root_format: &Option<String>,
548        formats: &[crate::displayed_item::FieldFormat],
549        translators: &TranslatorList,
550    ) -> Vec<SubFieldFlatTranslationResult> {
551        let sub_result = self.sub_format(formats, translators, &[]);
552
553        // FIXME for consistency we should not fall back to `translators.default` here, but fetch the
554        // preferred translator - but doing that ATM will break if the spade translator is used, since
555        // on the first render the spade translator seems to not have loaded its information yet.
556        let formatted = format(
557            &self.val,
558            self.kind,
559            root_format.as_ref().unwrap_or(&translators.default),
560            translators,
561            &sub_result,
562        );
563
564        let formatted = HierFormatResult {
565            names: vec![],
566            this: formatted,
567            fields: sub_result,
568        };
569        let mut collected = vec![];
570        formatted.collect_into(&mut collected);
571        collected
572    }
573}
574
575#[local_impl::local_impl]
576impl VariableInfoExt for VariableInfo {
577    fn get_subinfo(&self, path: &[String]) -> &VariableInfo {
578        match path {
579            [] => self,
580            [field, rest @ ..] => match self {
581                VariableInfo::Compound { subfields } => subfields
582                    .iter()
583                    .find(|(f, _)| f == field)
584                    .unwrap()
585                    .1
586                    .get_subinfo(rest),
587                VariableInfo::Bits => panic!(),
588                VariableInfo::Bool => panic!(),
589                VariableInfo::Clock => panic!(),
590                VariableInfo::String => panic!(),
591                VariableInfo::Real => panic!(),
592            },
593        }
594    }
595
596    fn has_subpath(&self, path: &[String]) -> bool {
597        match path {
598            [] => true,
599            [field, rest @ ..] => match self {
600                VariableInfo::Compound { subfields } => subfields
601                    .iter()
602                    .find(|&(f, _)| f == field)
603                    .is_some_and(|(_, info)| info.has_subpath(rest)),
604                _ => false,
605            },
606        }
607    }
608}
609
610#[local_impl::local_impl]
611impl ValueKindExt for ValueKind {
612    fn color(&self, user_color: Color32, theme: &SurferTheme) -> Color32 {
613        match self {
614            ValueKind::HighImp => theme.variable_highimp,
615            ValueKind::Undef => theme.variable_undef,
616            ValueKind::DontCare => theme.variable_dontcare,
617            ValueKind::Warn => theme.variable_undef,
618            ValueKind::Custom(custom_color) => *custom_color,
619            ValueKind::Weak => theme.variable_weak,
620            ValueKind::Normal => user_color,
621        }
622    }
623}
624
625pub struct StringTranslator {}
626
627impl Translator<VarId, ScopeId, Message> for StringTranslator {
628    fn name(&self) -> String {
629        "String".to_string()
630    }
631
632    fn translate(
633        &self,
634        _variable: &VariableMeta,
635        value: &VariableValue,
636    ) -> Result<TranslationResult> {
637        match value {
638            VariableValue::BigUint(b) => Ok(TranslationResult {
639                val: ValueRepr::String(format!("ERROR (0x{b:x})")),
640                kind: ValueKind::Warn,
641                subfields: vec![],
642            }),
643            VariableValue::String(s) => Ok(TranslationResult {
644                val: ValueRepr::String((*s).to_string()),
645                kind: ValueKind::Normal,
646                subfields: vec![],
647            }),
648        }
649    }
650
651    fn variable_info(&self, _variable: &VariableMeta) -> Result<VariableInfo> {
652        Ok(VariableInfo::String)
653    }
654
655    fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
656        // f64 (i.e. "real") values are treated as strings for now
657        if variable.encoding == VariableEncoding::String
658            || variable.encoding == VariableEncoding::Real
659        {
660            Ok(TranslationPreference::Prefer)
661        } else {
662            Ok(TranslationPreference::No)
663        }
664    }
665}
666
667fn check_single_wordlength(num_bits: Option<u32>, required: u32) -> Result<TranslationPreference> {
668    if let Some(num_bits) = num_bits {
669        if num_bits == required {
670            Ok(TranslationPreference::Yes)
671        } else {
672            Ok(TranslationPreference::No)
673        }
674    } else {
675        Ok(TranslationPreference::No)
676    }
677}
678
679fn match_variable_type_name(variable_type_name: &Option<String>, candidates: &[String]) -> bool {
680    if let Some(type_name) = variable_type_name {
681        for candidate in candidates {
682            if type_name.eq_ignore_ascii_case(candidate) {
683                return true;
684            }
685        }
686    }
687    false
688}