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