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 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#[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#[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 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#[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#[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 #[allow(unused_mut)]
376 let mut basic_translators: Vec<Arc<DynBasicTranslator>> = vec![
377 Arc::new(BitTranslator {}),
378 Arc::new(HexTranslator {}),
379 Arc::new(OctalTranslator {}),
380 Arc::new(GroupingBinaryTranslator {}),
381 Arc::new(BinaryTranslator {}),
382 Arc::new(ASCIITranslator {}),
383 Arc::new(new_rv32_translator()),
384 Arc::new(new_rv64_translator()),
385 Arc::new(new_mips_translator()),
386 Arc::new(new_la64_translator()),
387 Arc::new(LebTranslator {}),
388 Arc::new(UnsignedTranslator {}),
389 Arc::new(SignedTranslator {}),
390 Arc::new(SinglePrecisionTranslator {}),
391 Arc::new(DoublePrecisionTranslator {}),
392 Arc::new(HalfPrecisionTranslator {}),
393 Arc::new(BFloat16Translator {}),
394 Arc::new(Posit32Translator {}),
395 Arc::new(Posit16Translator {}),
396 Arc::new(Posit8Translator {}),
397 Arc::new(PositQuire8Translator {}),
398 Arc::new(PositQuire16Translator {}),
399 Arc::new(E5M2Translator {}),
400 Arc::new(E4M3Translator {}),
401 Arc::new(NumberOfOnesTranslator {}),
402 Arc::new(LeadingOnesTranslator {}),
403 Arc::new(TrailingOnesTranslator {}),
404 Arc::new(LeadingZerosTranslator {}),
405 Arc::new(TrailingZerosTranslator {}),
406 Arc::new(IdenticalMSBsTranslator {}),
407 #[cfg(feature = "f128")]
408 Arc::new(QuadPrecisionTranslator {}),
409 Arc::new(color_translators::RGBTranslator {}),
410 Arc::new(color_translators::GrayScaleTranslator {}),
411 Arc::new(color_translators::YCbCrTranslator {}),
412 ];
413
414 #[cfg(not(target_arch = "wasm32"))]
415 basic_translators.append(&mut find_user_decoders());
416
417 #[cfg(not(target_arch = "wasm32"))]
418 basic_translators.append(&mut find_user_mapping_translators());
419
420 TranslatorList::new(
421 basic_translators,
422 vec![
423 Arc::new(ClockTranslator::new()),
424 Arc::new(StringTranslator {}),
425 Arc::new(EnumTranslator {}),
426 Arc::new(UnsignedFixedPointTranslator),
427 Arc::new(SignedFixedPointTranslator),
428 Arc::new(EventTranslator {}),
429 ],
430 )
431}
432
433#[derive(Default)]
434pub struct TranslatorList {
435 inner: HashMap<String, AnyTranslator>,
436 #[cfg(feature = "python")]
437 python_translator: Option<(camino::Utf8PathBuf, String, AnyTranslator)>,
438 pub default: String,
439}
440
441impl TranslatorList {
442 #[must_use]
443 pub fn new(basic: Vec<Arc<DynBasicTranslator>>, translators: Vec<Arc<DynTranslator>>) -> Self {
444 Self {
445 default: "Hexadecimal".to_string(),
446 inner: basic
447 .into_iter()
448 .map(|t| (t.name(), AnyTranslator::Basic(t)))
449 .chain(
450 translators
451 .into_iter()
452 .map(|t| (t.name(), AnyTranslator::Full(t))),
453 )
454 .collect(),
455 #[cfg(feature = "python")]
456 python_translator: None,
457 }
458 }
459
460 pub fn all_translator_names(&self) -> Vec<&str> {
461 #[cfg(feature = "python")]
462 let python_name = self
463 .python_translator
464 .as_ref()
465 .map(|(_, name, _)| name.as_str());
466 #[cfg(not(feature = "python"))]
467 let python_name = None;
468 self.inner
469 .keys()
470 .map(String::as_str)
471 .chain(python_name)
472 .collect()
473 }
474
475 #[must_use]
476 pub fn all_translators(&self) -> Vec<&AnyTranslator> {
477 #[cfg(feature = "python")]
478 let python_translator = self.python_translator.as_ref().map(|(_, _, t)| t);
479 #[cfg(not(feature = "python"))]
480 let python_translator = None;
481 self.inner.values().chain(python_translator).collect()
482 }
483
484 #[must_use]
485 pub fn basic_translator_names(&self) -> Vec<&str> {
486 self.inner
487 .iter()
488 .filter_map(|(name, t)| t.is_basic().then_some(name.as_str()))
489 .collect()
490 }
491
492 #[must_use]
493 pub fn get_translator(&self, name: &str) -> &AnyTranslator {
494 #[cfg(feature = "python")]
495 let python_translator = || {
496 self.python_translator
497 .as_ref()
498 .filter(|(_, python_name, _)| python_name == name)
499 .map(|(_, _, t)| t)
500 };
501 #[cfg(not(feature = "python"))]
502 let python_translator = || None;
503 self.inner
504 .get(name)
505 .or_else(python_translator)
506 .unwrap_or_else(|| panic!("No translator called {name}"))
507 }
508
509 #[must_use]
510 pub fn clone_translator(&self, name: &str) -> AnyTranslator {
511 self.get_translator(name).clone()
512 }
513
514 pub fn add_or_replace(&mut self, t: AnyTranslator) {
515 self.inner.insert(t.name(), t);
516 }
517
518 #[must_use]
519 pub fn is_valid_translator(&self, meta: &VariableMeta, candidate: &str) -> bool {
520 self.get_translator(candidate)
521 .translates(meta)
522 .is_ok_and(|preference| preference != TranslationPreference::No)
523 }
524
525 #[cfg(feature = "python")]
526 pub fn load_python_translator(&mut self, filename: camino::Utf8PathBuf) -> Result<()> {
527 tracing::debug!("Reading Python code from disk: {filename}");
528 let code = std::ffi::CString::new(std::fs::read_to_string(&filename)?)?;
529 let mut translators = python_translators::PythonTranslator::new(&code.as_c_str())?;
530 if translators.len() != 1 {
531 eyre::bail!("Only one Python translator per file is supported for now");
532 }
533 let translator = translators.pop().unwrap();
534 self.python_translator = Some((
535 filename,
536 translator.name(),
537 AnyTranslator::Python(Arc::new(translator)),
538 ));
539 Ok(())
540 }
541
542 #[cfg(feature = "python")]
543 pub fn has_python_translator(&self) -> bool {
544 self.python_translator.is_some()
545 }
546
547 #[cfg(feature = "python")]
548 pub fn reload_python_translator(&mut self) -> Result<()> {
549 if let Some((path, _, _)) = self.python_translator.take() {
550 self.load_python_translator(path)?;
551 }
552 Ok(())
553 }
554}
555
556fn format(
557 val: &ValueRepr,
558 kind: ValueKind,
559 subtranslator_name: &String,
560 translators: &TranslatorList,
561 subresults: &[HierFormatResult],
562) -> Option<TranslatedValue> {
563 match val {
564 ValueRepr::Bit(val) => {
565 let AnyTranslator::Basic(subtranslator) =
566 translators.get_translator(subtranslator_name)
567 else {
568 panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
569 };
570
571 Some(TranslatedValue::from_basic_translate(
572 subtranslator.basic_translate(1, &VariableValue::String(val.to_string())),
573 ))
574 }
575 ValueRepr::Bits(bit_count, bits) => {
576 let AnyTranslator::Basic(subtranslator) =
577 translators.get_translator(subtranslator_name)
578 else {
579 panic!("Subtranslator '{subtranslator_name}' was not a basic translator");
580 };
581
582 Some(TranslatedValue::from_basic_translate(
583 subtranslator.basic_translate(*bit_count, &VariableValue::String(bits.clone())),
584 ))
585 }
586 ValueRepr::String(sval) => Some(TranslatedValue {
587 value: sval.clone(),
588 kind,
589 }),
590 ValueRepr::Tuple => Some(TranslatedValue {
591 value: format!(
592 "({})",
593 subresults
594 .iter()
595 .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
596 .join(", ")
597 ),
598 kind,
599 }),
600 ValueRepr::Struct => Some(TranslatedValue {
601 value: format!(
602 "{{{}}}",
603 subresults
604 .iter()
605 .map(|v| {
606 let n = v.names.join("_");
607 format!("{n}: {}", v.this.as_ref().map_or("-", |t| t.value.as_str()))
608 })
609 .join(", ")
610 ),
611 kind,
612 }),
613 ValueRepr::Array => Some(TranslatedValue {
614 value: format!(
615 "[{}]",
616 subresults
617 .iter()
618 .map(|v| v.this.as_ref().map_or("-", |t| t.value.as_str()))
619 .join(", ")
620 ),
621 kind,
622 }),
623 ValueRepr::NotPresent => None,
624 ValueRepr::Enum { idx, name } => Some(TranslatedValue {
625 value: format!(
626 "{name}{{{}}}",
627 subresults[*idx]
628 .this
629 .as_ref()
630 .map_or("-", |t| t.value.as_str())
631 ),
632 kind,
633 }),
634 ValueRepr::Event => Some(TranslatedValue {
635 value: "Event".to_string(),
636 kind,
637 }),
638 }
639}
640
641#[local_impl::local_impl]
642impl TranslationResultExt for TranslationResult {
643 fn sub_format(
644 &self,
645 formats: &[crate::displayed_item::FieldFormat],
646 translators: &TranslatorList,
647 path_so_far: &[String],
648 ) -> Vec<HierFormatResult> {
649 self.subfields
650 .iter()
651 .map(|res| {
652 let sub_path = path_so_far
653 .iter()
654 .chain([&res.name])
655 .cloned()
656 .collect::<Vec<_>>();
657
658 let sub = res.result.sub_format(formats, translators, &sub_path);
659
660 let translator_name = formats
663 .iter()
664 .find(|e| e.field == sub_path)
665 .map(|e| e.format.clone())
666 .unwrap_or(translators.default.clone());
667 let formatted = format(
668 &res.result.val,
669 res.result.kind,
670 &translator_name,
671 translators,
672 &sub,
673 );
674
675 HierFormatResult {
676 this: formatted,
677 names: sub_path,
678 fields: sub,
679 }
680 })
681 .collect::<Vec<_>>()
682 }
683
684 fn format_flat(
686 &self,
687 root_format: &Option<String>,
688 formats: &[crate::displayed_item::FieldFormat],
689 translators: &TranslatorList,
690 ) -> Vec<SubFieldFlatTranslationResult> {
691 let sub_result = self.sub_format(formats, translators, &[]);
692
693 let formatted = format(
697 &self.val,
698 self.kind,
699 root_format.as_ref().unwrap_or(&translators.default),
700 translators,
701 &sub_result,
702 );
703
704 let formatted = HierFormatResult {
705 names: vec![],
706 this: formatted,
707 fields: sub_result,
708 };
709 let mut collected = vec![];
710 formatted.collect_into(&mut collected);
711 collected
712 }
713}
714
715#[local_impl::local_impl]
716impl VariableInfoExt for VariableInfo {
717 fn get_subinfo(&self, path: &[String]) -> &VariableInfo {
718 match path {
719 [] => self,
720 [field, rest @ ..] => match self {
721 VariableInfo::Compound { subfields } => subfields
722 .iter()
723 .find(|(f, _)| f == field)
724 .unwrap()
725 .1
726 .get_subinfo(rest),
727 VariableInfo::Bits => panic!(),
728 VariableInfo::Bool => panic!(),
729 VariableInfo::Clock => panic!(),
730 VariableInfo::String => panic!(),
731 VariableInfo::Real => panic!(),
732 VariableInfo::Event => panic!(),
733 },
734 }
735 }
736
737 fn has_subpath(&self, path: &[String]) -> bool {
738 match path {
739 [] => true,
740 [field, rest @ ..] => match self {
741 VariableInfo::Compound { subfields } => subfields
742 .iter()
743 .find(|&(f, _)| f == field)
744 .is_some_and(|(_, info)| info.has_subpath(rest)),
745 _ => false,
746 },
747 }
748 }
749}
750
751#[local_impl::local_impl]
752impl ValueKindExt for ValueKind {
753 fn color(&self, user_color: Color32, theme: &SurferTheme) -> Color32 {
754 match self {
755 ValueKind::HighImp => theme.variable_highimp,
756 ValueKind::Undef => theme.variable_undef,
757 ValueKind::DontCare => theme.variable_dontcare,
758 ValueKind::Warn => theme.variable_undef,
759 ValueKind::Custom(custom_color) => *custom_color,
760 ValueKind::Weak => theme.variable_weak,
761 ValueKind::Error => theme.accent_error.background,
762 ValueKind::Normal => user_color,
763 ValueKind::Event => theme.variable_event,
764 }
765 }
766}
767
768pub struct StringTranslator {}
769
770impl Translator<VarId, ScopeId, Message> for StringTranslator {
771 fn name(&self) -> String {
772 "String".to_string()
773 }
774
775 fn translate(
776 &self,
777 _variable: &VariableMeta,
778 value: &VariableValue,
779 ) -> Result<TranslationResult> {
780 match value {
781 VariableValue::BigUint(b) => Ok(TranslationResult {
782 val: ValueRepr::String(format!("ERROR (0x{b:x})")),
783 kind: ValueKind::Warn,
784 subfields: vec![],
785 }),
786 VariableValue::String(s) => Ok(TranslationResult {
787 val: ValueRepr::String((*s).clone()),
788 kind: ValueKind::Normal,
789 subfields: vec![],
790 }),
791 }
792 }
793
794 fn variable_info(&self, _variable: &VariableMeta) -> Result<VariableInfo> {
795 Ok(VariableInfo::String)
796 }
797
798 fn translates(&self, variable: &VariableMeta) -> Result<TranslationPreference> {
799 if variable.encoding == VariableEncoding::String {
800 Ok(TranslationPreference::Prefer)
801 } else {
802 Ok(TranslationPreference::No)
803 }
804 }
805}
806
807fn check_single_wordlength(num_bits: Option<u32>, required: u32) -> Result<TranslationPreference> {
808 if Some(required) == num_bits {
809 Ok(TranslationPreference::Yes)
810 } else {
811 Ok(TranslationPreference::No)
812 }
813}
814
815#[cfg(test)]
816mod tests {
817 use super::*;
818
819 #[test]
820 fn check_single_wordlength_exact_match() {
821 let result = check_single_wordlength(Some(32), 32).unwrap();
822 assert_eq!(result, TranslationPreference::Yes);
823 }
824
825 #[test]
826 fn check_single_wordlength_mismatch() {
827 let result = check_single_wordlength(Some(64), 32).unwrap();
828 assert_eq!(result, TranslationPreference::No);
829 }
830
831 #[test]
832 fn check_single_wordlength_none() {
833 let result = check_single_wordlength(None, 32).unwrap();
834 assert_eq!(result, TranslationPreference::No);
835 }
836
837 #[test]
838 fn translator_list_basic_operations() {
839 let translators = all_translators();
840
841 assert!(!translators.all_translator_names().is_empty());
843
844 assert!(
846 translators
847 .all_translator_names()
848 .contains(&translators.default.as_str())
849 );
850
851 let hex_translator = translators.get_translator("Hexadecimal");
853 assert_eq!(hex_translator.name(), "Hexadecimal");
854
855 let basic_names = translators.basic_translator_names();
857 assert!(basic_names.contains(&"Hexadecimal"));
858 assert!(basic_names.contains(&"Binary"));
859 }
860
861 #[test]
862 fn variable_info_has_subpath() {
863 use surfer_translation_types::VariableInfo;
864
865 let info = VariableInfo::Compound {
866 subfields: vec![
867 ("field1".to_string(), VariableInfo::Bits),
868 (
869 "field2".to_string(),
870 VariableInfo::Compound {
871 subfields: vec![("nested".to_string(), VariableInfo::Bool)],
872 },
873 ),
874 ],
875 };
876
877 assert!(info.has_subpath(&[]));
878 assert!(info.has_subpath(&["field1".to_string()]));
879 assert!(info.has_subpath(&["field2".to_string()]));
880 assert!(info.has_subpath(&["field2".to_string(), "nested".to_string()]));
881 assert!(!info.has_subpath(&["nonexistent".to_string()]));
882 assert!(!info.has_subpath(&["field2".to_string(), "nonexistent".to_string()]));
883 }
884}